GCC Code Coverage Report


Directory: libs/beast2/
File: src/server/serve_static.cpp
Date: 2025-11-13 15:50:44
Exec Total Coverage
Lines: 0 91 0.0%
Functions: 0 8 0.0%
Branches: 0 110 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 Request& req,
174 Response& res) const ->
175 route_result
176 {
177 // Allow: GET, HEAD
178 if( req.m.method() != http_proto::method::get &&
179 req.m.method() != http_proto::method::head)
180 {
181 if(impl_->opt.fallthrough)
182 return route::next;
183
184 res.m.set_status(
185 http_proto::status::method_not_allowed);
186 res.m.set(http_proto::field::allow, "GET, HEAD");
187 res.set_body("");
188 return route::send;
189 }
190
191 // Build the path to the requested file
192 std::string path;
193 path_cat(path, impl_->path, req.path);
194 if(req.pr.get().target().back() == '/')
195 {
196 path.push_back('/');
197 path.append("index.html");
198 }
199
200 // Attempt to open the file
201 system::error_code ec;
202 http_proto::file f;
203 std::uint64_t size = 0;
204 f.open(path.c_str(), http_proto::file_mode::scan, ec);
205 if(! ec.failed())
206 size = f.size(ec);
207 if(! ec.failed())
208 {
209 res.m.set_start_line(
210 http_proto::status::ok,
211 req.m.version());
212 res.m.set_payload_size(size);
213
214 auto mt = mime_type(get_extension(path));
215 res.m.append(
216 http_proto::field::content_type, mt);
217
218 // send file
219 res.sr.start<http_proto::file_source>(
220 res.m, std::move(f), size);
221 return route::send;
222 }
223
224 if( ec == system::errc::no_such_file_or_directory &&
225 ! impl_->opt.fallthrough)
226 return route::next;
227
228 BOOST_ASSERT(ec.failed());
229 return ec;
230 }
231
232 } // beast2
233 } // boost
234
235