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