Line data Source code
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 0 : get_extension(
25 : core::string_view path) noexcept
26 : {
27 0 : auto const pos = path.rfind(".");
28 0 : if( pos == core::string_view::npos)
29 0 : return core::string_view();
30 0 : return path.substr(pos);
31 : }
32 :
33 : static
34 : core::string_view
35 0 : mime_type(
36 : core::string_view path)
37 : {
38 : using urls::grammar::ci_is_equal;
39 0 : auto ext = get_extension(path);
40 0 : if(ci_is_equal(ext, ".htm")) return "text/html";
41 0 : if(ci_is_equal(ext, ".html")) return "text/html";
42 0 : if(ci_is_equal(ext, ".php")) return "text/html";
43 0 : if(ci_is_equal(ext, ".css")) return "text/css";
44 0 : if(ci_is_equal(ext, ".txt")) return "text/plain";
45 0 : if(ci_is_equal(ext, ".js")) return "application/javascript";
46 0 : if(ci_is_equal(ext, ".json")) return "application/json";
47 0 : if(ci_is_equal(ext, ".xml")) return "application/xml";
48 0 : if(ci_is_equal(ext, ".swf")) return "application/x-shockwave-flash";
49 0 : if(ci_is_equal(ext, ".flv")) return "video/x-flv";
50 0 : if(ci_is_equal(ext, ".png")) return "image/png";
51 0 : if(ci_is_equal(ext, ".jpe")) return "image/jpeg";
52 0 : if(ci_is_equal(ext, ".jpeg")) return "image/jpeg";
53 0 : if(ci_is_equal(ext, ".jpg")) return "image/jpeg";
54 0 : if(ci_is_equal(ext, ".gif")) return "image/gif";
55 0 : if(ci_is_equal(ext, ".bmp")) return "image/bmp";
56 0 : if(ci_is_equal(ext, ".ico")) return "image/vnd.microsoft.icon";
57 0 : if(ci_is_equal(ext, ".tiff")) return "image/tiff";
58 0 : if(ci_is_equal(ext, ".tif")) return "image/tiff";
59 0 : if(ci_is_equal(ext, ".svg")) return "image/svg+xml";
60 0 : if(ci_is_equal(ext, ".svgz")) return "image/svg+xml";
61 0 : 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 0 : path_cat(
101 : std::string& result,
102 : core::string_view prefix,
103 : core::string_view suffix)
104 : {
105 0 : result = prefix;
106 :
107 : #ifdef BOOST_MSVC
108 : char constexpr path_separator = '\\';
109 : #else
110 0 : char constexpr path_separator = '/';
111 : #endif
112 0 : if( result.back() == path_separator)
113 0 : 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 0 : for(auto const& c : suffix)
120 : {
121 0 : if(c == '/')
122 0 : result.push_back(path_separator);
123 : else
124 0 : result.push_back(c);
125 : }
126 0 : }
127 :
128 : //------------------------------------------------
129 :
130 : // serve-static
131 : //
132 : // https://www.npmjs.com/package/serve-static
133 :
134 : struct serve_static::impl
135 : {
136 0 : impl(
137 : core::string_view path_,
138 : options const& opt_)
139 0 : : path(path_)
140 0 : , opt(opt_)
141 : {
142 0 : }
143 :
144 : std::string path;
145 : options opt;
146 : };
147 :
148 0 : serve_static::
149 : ~serve_static()
150 : {
151 0 : if(impl_)
152 0 : delete impl_;
153 0 : }
154 :
155 0 : serve_static::
156 0 : serve_static(serve_static&& other) noexcept
157 0 : : impl_(other.impl_)
158 : {
159 0 : other.impl_ = nullptr;
160 0 : }
161 :
162 0 : serve_static::
163 : serve_static(
164 : core::string_view path,
165 0 : options const& opt)
166 0 : : impl_(new impl(path, opt))
167 : {
168 0 : }
169 :
170 : auto
171 0 : serve_static::
172 : operator()(
173 : Request& req,
174 : Response& res) const ->
175 : route_result
176 : {
177 : // Allow: GET, HEAD
178 0 : if( req.m.method() != http_proto::method::get &&
179 0 : req.m.method() != http_proto::method::head)
180 : {
181 0 : if(impl_->opt.fallthrough)
182 0 : return route::next;
183 :
184 0 : res.m.set_status(
185 : http_proto::status::method_not_allowed);
186 0 : res.m.set(http_proto::field::allow, "GET, HEAD");
187 0 : res.set_body("");
188 0 : return route::send;
189 : }
190 :
191 : // Build the path to the requested file
192 0 : std::string path;
193 0 : path_cat(path, impl_->path, req.path);
194 0 : if(req.pr.get().target().back() == '/')
195 : {
196 0 : path.push_back('/');
197 0 : path.append("index.html");
198 : }
199 :
200 : // Attempt to open the file
201 0 : system::error_code ec;
202 0 : http_proto::file f;
203 0 : std::uint64_t size = 0;
204 0 : f.open(path.c_str(), http_proto::file_mode::scan, ec);
205 0 : if(! ec.failed())
206 0 : size = f.size(ec);
207 0 : if(! ec.failed())
208 : {
209 0 : res.m.set_start_line(
210 : http_proto::status::ok,
211 0 : req.m.version());
212 0 : res.m.set_payload_size(size);
213 :
214 0 : auto mt = mime_type(get_extension(path));
215 0 : res.m.append(
216 : http_proto::field::content_type, mt);
217 :
218 : // send file
219 0 : res.sr.start<http_proto::file_source>(
220 0 : res.m, std::move(f), size);
221 0 : return route::send;
222 : }
223 :
224 0 : if( ec == system::errc::no_such_file_or_directory &&
225 0 : ! impl_->opt.fallthrough)
226 0 : return route::next;
227 :
228 0 : BOOST_ASSERT(ec.failed());
229 0 : return ec;
230 0 : }
231 :
232 : } // beast2
233 : } // boost
234 :
|