GCC Code Coverage Report


Directory: ./
File: libs/beast2/src/server/serve_static.cpp
Date: 2025-12-24 17:07:59
Exec Total Coverage
Lines: 0 90 0.0%
Functions: 0 8 0.0%
Branches: 0 94 0.0%

Line Branch Exec Source
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/beast2
8 //
9
10 #include <boost/beast2/server/serve_static.hpp>
11 #include <boost/beast2/error.hpp>
12 #include <boost/http_proto/file_source.hpp>
13 #include <boost/url/grammar/ci_string.hpp>
14 #include <string>
15
16 namespace boost {
17 namespace beast2 {
18
19 //------------------------------------------------
20
21 // Return a reasonable mime type based on the extension of a file.
22 static
23 core::string_view
24 get_extension(
25 core::string_view path) noexcept
26 {
27 auto const pos = path.rfind(".");
28 if( pos == core::string_view::npos)
29 return core::string_view();
30 return path.substr(pos);
31 }
32
33 static
34 core::string_view
35 mime_type(
36 core::string_view path)
37 {
38 using urls::grammar::ci_is_equal;
39 auto ext = get_extension(path);
40 if(ci_is_equal(ext, ".htm")) return "text/html";
41 if(ci_is_equal(ext, ".html")) return "text/html";
42 if(ci_is_equal(ext, ".php")) return "text/html";
43 if(ci_is_equal(ext, ".css")) return "text/css";
44 if(ci_is_equal(ext, ".txt")) return "text/plain";
45 if(ci_is_equal(ext, ".js")) return "application/javascript";
46 if(ci_is_equal(ext, ".json")) return "application/json";
47 if(ci_is_equal(ext, ".xml")) return "application/xml";
48 if(ci_is_equal(ext, ".swf")) return "application/x-shockwave-flash";
49 if(ci_is_equal(ext, ".flv")) return "video/x-flv";
50 if(ci_is_equal(ext, ".png")) return "image/png";
51 if(ci_is_equal(ext, ".jpe")) return "image/jpeg";
52 if(ci_is_equal(ext, ".jpeg")) return "image/jpeg";
53 if(ci_is_equal(ext, ".jpg")) return "image/jpeg";
54 if(ci_is_equal(ext, ".gif")) return "image/gif";
55 if(ci_is_equal(ext, ".bmp")) return "image/bmp";
56 if(ci_is_equal(ext, ".ico")) return "image/vnd.microsoft.icon";
57 if(ci_is_equal(ext, ".tiff")) return "image/tiff";
58 if(ci_is_equal(ext, ".tif")) return "image/tiff";
59 if(ci_is_equal(ext, ".svg")) return "image/svg+xml";
60 if(ci_is_equal(ext, ".svgz")) return "image/svg+xml";
61 return "application/text";
62 }
63
64 #if 0
65 // Append an HTTP rel-path to a local filesystem path.
66 // The returned path is normalized for the platform.
67 static
68 void
69 path_cat(
70 std::string& result,
71 core::string_view prefix,
72 urls::segments_view suffix)
73 {
74 result = prefix;
75
76 #ifdef BOOST_MSVC
77 char constexpr path_separator = '\\';
78 #else
79 char constexpr path_separator = '/';
80 #endif
81 if( result.back() == path_separator)
82 result.resize(result.size() - 1); // remove trailing
83 #ifdef BOOST_MSVC
84 for(auto& c : result)
85 if( c == '/')
86 c = path_separator;
87 #endif
88 for(auto const& seg : suffix)
89 {
90 result.push_back(path_separator);
91 result.append(seg);
92 }
93 }
94 #endif
95
96 // Append an HTTP rel-path to a local filesystem path.
97 // The returned path is normalized for the platform.
98 static
99 void
100 path_cat(
101 std::string& result,
102 core::string_view prefix,
103 core::string_view suffix)
104 {
105 result = prefix;
106
107 #ifdef BOOST_MSVC
108 char constexpr path_separator = '\\';
109 #else
110 char constexpr path_separator = '/';
111 #endif
112 if( result.back() == path_separator)
113 result.resize(result.size() - 1); // remove trailing
114 #ifdef BOOST_MSVC
115 for(auto& c : result)
116 if( c == '/')
117 c = path_separator;
118 #endif
119 for(auto const& c : suffix)
120 {
121 if(c == '/')
122 result.push_back(path_separator);
123 else
124 result.push_back(c);
125 }
126 }
127
128 //------------------------------------------------
129
130 // serve-static
131 //
132 // https://www.npmjs.com/package/serve-static
133
134 struct serve_static::impl
135 {
136 impl(
137 core::string_view path_,
138 options const& opt_)
139 : path(path_)
140 , opt(opt_)
141 {
142 }
143
144 std::string path;
145 options opt;
146 };
147
148 serve_static::
149 ~serve_static()
150 {
151 if(impl_)
152 delete impl_;
153 }
154
155 serve_static::
156 serve_static(serve_static&& other) noexcept
157 : impl_(other.impl_)
158 {
159 other.impl_ = nullptr;
160 }
161
162 serve_static::
163 serve_static(
164 core::string_view path,
165 options const& opt)
166 : impl_(new impl(path, opt))
167 {
168 }
169
170 auto
171 serve_static::
172 operator()(
173 http::route_params& p) const ->
174 http::route_result
175 {
176 // Allow: GET, HEAD
177 if( p.req.method() != http::method::get &&
178 p.req.method() != http::method::head)
179 {
180 if(impl_->opt.fallthrough)
181 return http::route::next;
182
183 p.res.set_status(
184 http::status::method_not_allowed);
185 p.res.set(http::field::allow, "GET, HEAD");
186 p.set_body("");
187 return http::route::send;
188 }
189
190 // Build the path to the requested file
191 std::string path;
192 path_cat(path, impl_->path, p.path);
193 if(p.parser.get().target().back() == '/')
194 {
195 path.push_back('/');
196 path.append("index.html");
197 }
198
199 // Attempt to open the file
200 system::error_code ec;
201 capy::file f;
202 std::uint64_t size = 0;
203 f.open(path.c_str(), capy::file_mode::scan, ec);
204 if(! ec.failed())
205 size = f.size(ec);
206 if(! ec.failed())
207 {
208 p.res.set_start_line(
209 http::status::ok,
210 p.req.version());
211 p.res.set_payload_size(size);
212
213 auto mt = mime_type(get_extension(path));
214 p.res.append(
215 http::field::content_type, mt);
216
217 // send file
218 p.serializer.start<http::file_source>(
219 p.res, std::move(f), size);
220 return http::route::send;
221 }
222
223 if( ec == system::errc::no_such_file_or_directory &&
224 ! impl_->opt.fallthrough)
225 return http::route::next;
226
227 BOOST_ASSERT(ec.failed());
228 return ec;
229 }
230
231 } // beast2
232 } // boost
233
234