Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/http
8 : //
9 :
10 : #include <boost/http/server/route_handler.hpp>
11 : #include <boost/http/server/etag.hpp>
12 : #include <boost/http/server/fresh.hpp>
13 : #include <boost/http/detail/except.hpp>
14 : #include <boost/url/grammar/ci_string.hpp>
15 : #include <boost/capy/buffers/make_buffer.hpp>
16 : #include <boost/assert.hpp>
17 : #include <cstring>
18 :
19 : namespace boost {
20 : namespace system {
21 : template<>
22 : struct is_error_code_enum<
23 : ::boost::http::route_what>
24 : {
25 : static bool const value = true;
26 : };
27 : } // system
28 : } // boost
29 :
30 : namespace boost {
31 : namespace http {
32 :
33 : namespace {
34 :
35 : struct route_what_cat_type
36 : : system::error_category
37 : {
38 : constexpr route_what_cat_type()
39 : : error_category(0x7a8b3c4d5e6f1029)
40 : {
41 : }
42 :
43 0 : const char* name() const noexcept override
44 : {
45 0 : return "boost.http.route_what";
46 : }
47 :
48 0 : std::string message(int code) const override
49 : {
50 0 : return message(code, nullptr, 0);
51 : }
52 :
53 0 : char const* message(
54 : int code,
55 : char*,
56 : std::size_t) const noexcept override
57 : {
58 0 : switch(static_cast<route_what>(code))
59 : {
60 0 : case route_what::done: return "done";
61 0 : case route_what::error: return "error";
62 0 : case route_what::next: return "next";
63 0 : case route_what::next_route: return "next_route";
64 0 : case route_what::close: return "close";
65 0 : default:
66 0 : return "?";
67 : }
68 : }
69 : };
70 :
71 : route_what_cat_type route_what_cat;
72 :
73 : } // (anon)
74 :
75 : system::error_code
76 334 : make_error_code(
77 : route_what w) noexcept
78 : {
79 : return system::error_code{
80 334 : static_cast<int>(w), route_what_cat};
81 : }
82 :
83 : //----------------------------------------------------------
84 :
85 45 : route_result::
86 : route_result(
87 45 : system::error_code ec)
88 45 : : ec_(ec)
89 : {
90 45 : if(! ec.failed())
91 0 : detail::throw_invalid_argument();
92 45 : }
93 :
94 : void
95 244 : route_result::
96 : set(route_what w)
97 : {
98 244 : ec_ = make_error_code(w);
99 244 : }
100 :
101 : auto
102 910 : route_result::
103 : what() const noexcept ->
104 : route_what
105 : {
106 910 : if(! ec_.failed())
107 686 : return route_what::done;
108 224 : if(&ec_.category() != &route_what_cat)
109 147 : return route_what::error;
110 77 : if( ec_ == route_what::next)
111 64 : return route_what::next;
112 13 : if( ec_ == route_what::next_route)
113 7 : return route_what::next_route;
114 : //if( ec_ == route_what::close)
115 6 : return route_what::close;
116 : }
117 :
118 : auto
119 20 : route_result::
120 : error() const noexcept ->
121 : system::error_code
122 : {
123 20 : if(&ec_.category() != &route_what_cat)
124 20 : return ec_;
125 0 : return {};
126 : }
127 :
128 : //------------------------------------------------
129 :
130 : bool
131 0 : route_params::
132 : is_method(
133 : core::string_view s) const noexcept
134 : {
135 0 : auto m = http::string_to_method(s);
136 0 : if(m != http::method::unknown)
137 0 : return priv_.verb_ == m;
138 0 : return s == priv_.verb_str_;
139 : }
140 :
141 : //------------------------------------------------
142 :
143 : capy::io_task<>
144 108 : route_params::
145 : send(std::string_view body)
146 : {
147 : auto const sc = res.status();
148 :
149 : // 204 No Content / 304 Not Modified: strip headers, no body
150 : if(sc == status::no_content ||
151 : sc == status::not_modified)
152 : {
153 : res.erase(field::content_type);
154 : res.erase(field::content_length);
155 : res.erase(field::transfer_encoding);
156 : co_return co_await res_body.write_eof();
157 : }
158 :
159 : // 205 Reset Content: Content-Length=0, no body
160 : if(sc == status::reset_content)
161 : {
162 : res.erase(field::transfer_encoding);
163 : res.set_payload_size(0);
164 : co_return co_await res_body.write_eof();
165 : }
166 :
167 : // Set Content-Type if not already set
168 : if(! res.exists(field::content_type))
169 : {
170 : if(! body.empty() && body[0] == '<')
171 : res.set(field::content_type,
172 : "text/html; charset=utf-8");
173 : else
174 : res.set(field::content_type,
175 : "text/plain; charset=utf-8");
176 : }
177 :
178 : // Generate ETag if not already set
179 : if(! res.exists(field::etag))
180 : res.set(field::etag, etag(body));
181 :
182 : // Set Content-Length if not already set
183 : if(! res.exists(field::content_length))
184 : res.set_payload_size(body.size());
185 :
186 : // Freshness check: auto-304 for conditional GET
187 : if(is_fresh(req, res))
188 : {
189 : res.set_status(status::not_modified);
190 : res.erase(field::content_type);
191 : res.erase(field::content_length);
192 : res.erase(field::transfer_encoding);
193 : co_return co_await res_body.write_eof();
194 : }
195 :
196 : // HEAD: send headers only, skip body
197 : if(req.method() == method::head)
198 : {
199 : co_return co_await res_body.write_eof();
200 : }
201 :
202 : auto [ec, n] = co_await res_body.write_eof(
203 : capy::make_buffer(body));
204 : co_return {ec};
205 216 : }
206 :
207 : } // http
208 : } // boost
|