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