1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/http
7  
// Official repository: https://github.com/cppalliance/http
8  
//
8  
//
9  

9  

10  
#include <boost/http/server/route_handler.hpp>
10  
#include <boost/http/server/route_handler.hpp>
11  
#include <boost/http/server/etag.hpp>
11  
#include <boost/http/server/etag.hpp>
12  
#include <boost/http/server/fresh.hpp>
12  
#include <boost/http/server/fresh.hpp>
13  
#include <boost/http/detail/except.hpp>
13  
#include <boost/http/detail/except.hpp>
14  
#include <boost/url/grammar/ci_string.hpp>
14  
#include <boost/url/grammar/ci_string.hpp>
15  
#include <boost/capy/buffers/make_buffer.hpp>
15  
#include <boost/capy/buffers/make_buffer.hpp>
16  
#include <boost/assert.hpp>
16  
#include <boost/assert.hpp>
17  
#include <cstring>
17  
#include <cstring>
18  

18  

19  
namespace boost {
19  
namespace boost {
20  
namespace system {
20  
namespace system {
21  
template<>
21  
template<>
22  
struct is_error_code_enum<
22  
struct is_error_code_enum<
23  
    ::boost::http::route_what>
23  
    ::boost::http::route_what>
24  
{
24  
{
25  
    static bool const value = true;
25  
    static bool const value = true;
26  
};
26  
};
27  
} // system
27  
} // system
28  
} // boost
28  
} // boost
29  

29  

30  
namespace boost {
30  
namespace boost {
31  
namespace http {
31  
namespace http {
32  

32  

33  
namespace {
33  
namespace {
34  

34  

35  
struct route_what_cat_type
35  
struct route_what_cat_type
36  
    : system::error_category
36  
    : system::error_category
37  
{
37  
{
38  
    constexpr route_what_cat_type()
38  
    constexpr route_what_cat_type()
39  
        : error_category(0x7a8b3c4d5e6f1029)
39  
        : error_category(0x7a8b3c4d5e6f1029)
40  
    {
40  
    {
41  
    }
41  
    }
42  

42  

43  
    const char* name() const noexcept override
43  
    const char* name() const noexcept override
44  
    {
44  
    {
45  
        return "boost.http.route_what";
45  
        return "boost.http.route_what";
46  
    }
46  
    }
47  

47  

48  
    std::string message(int code) const override
48  
    std::string message(int code) const override
49  
    {
49  
    {
50  
        return message(code, nullptr, 0);
50  
        return message(code, nullptr, 0);
51  
    }
51  
    }
52  

52  

53  
    char const* message(
53  
    char const* message(
54  
        int code,
54  
        int code,
55  
        char*,
55  
        char*,
56  
        std::size_t) const noexcept override
56  
        std::size_t) const noexcept override
57  
    {
57  
    {
58  
        switch(static_cast<route_what>(code))
58  
        switch(static_cast<route_what>(code))
59  
        {
59  
        {
60  
        case route_what::done:          return "done";
60  
        case route_what::done:          return "done";
61  
        case route_what::error:         return "error";
61  
        case route_what::error:         return "error";
62  
        case route_what::next:          return "next";
62  
        case route_what::next:          return "next";
63  
        case route_what::next_route:    return "next_route";
63  
        case route_what::next_route:    return "next_route";
64  
        case route_what::close:         return "close";
64  
        case route_what::close:         return "close";
65  
        default:
65  
        default:
66  
            return "?";
66  
            return "?";
67  
        }
67  
        }
68  
    }
68  
    }
69  
};
69  
};
70  

70  

71  
route_what_cat_type route_what_cat;
71  
route_what_cat_type route_what_cat;
72  

72  

73  
} // (anon)
73  
} // (anon)
74  

74  

75  
system::error_code
75  
system::error_code
76  
make_error_code(
76  
make_error_code(
77  
    route_what w) noexcept
77  
    route_what w) noexcept
78  
{
78  
{
79  
    return system::error_code{
79  
    return system::error_code{
80  
        static_cast<int>(w), route_what_cat};
80  
        static_cast<int>(w), route_what_cat};
81  
}
81  
}
82  

82  

83  
//----------------------------------------------------------
83  
//----------------------------------------------------------
84  

84  

85  
route_result::
85  
route_result::
86  
route_result(
86  
route_result(
87  
    system::error_code ec)
87  
    system::error_code ec)
88  
    : ec_(ec)
88  
    : ec_(ec)
89  
{
89  
{
90  
    if(! ec.failed())
90  
    if(! ec.failed())
91  
        detail::throw_invalid_argument();
91  
        detail::throw_invalid_argument();
92  
}
92  
}
93  

93  

94  
void
94  
void
95  
route_result::
95  
route_result::
96  
set(route_what w)
96  
set(route_what w)
97  
{
97  
{
98  
    ec_ = make_error_code(w);
98  
    ec_ = make_error_code(w);
99  
}
99  
}
100  

100  

101  
auto
101  
auto
102  
route_result::
102  
route_result::
103  
what() const noexcept ->
103  
what() const noexcept ->
104  
    route_what
104  
    route_what
105  
{
105  
{
106  
    if(! ec_.failed())
106  
    if(! ec_.failed())
107  
        return route_what::done;    
107  
        return route_what::done;    
108  
    if(&ec_.category() != &route_what_cat)
108  
    if(&ec_.category() != &route_what_cat)
109  
        return route_what::error;
109  
        return route_what::error;
110  
    if( ec_ == route_what::next)
110  
    if( ec_ == route_what::next)
111  
        return route_what::next;
111  
        return route_what::next;
112  
    if( ec_ == route_what::next_route)
112  
    if( ec_ == route_what::next_route)
113  
        return route_what::next_route;
113  
        return route_what::next_route;
114  
    //if( ec_ == route_what::close)
114  
    //if( ec_ == route_what::close)
115  
    return route_what::close;
115  
    return route_what::close;
116  
}
116  
}
117  

117  

118  
auto
118  
auto
119  
route_result::
119  
route_result::
120  
error() const noexcept ->
120  
error() const noexcept ->
121  
    system::error_code
121  
    system::error_code
122  
{
122  
{
123  
    if(&ec_.category() != &route_what_cat)
123  
    if(&ec_.category() != &route_what_cat)
124  
        return ec_;
124  
        return ec_;
125  
    return {};
125  
    return {};
126  
}
126  
}
127  

127  

128  
//------------------------------------------------
128  
//------------------------------------------------
129  

129  

130  
bool
130  
bool
131  
route_params::
131  
route_params::
132  
is_method(
132  
is_method(
133  
    core::string_view s) const noexcept
133  
    core::string_view s) const noexcept
134  
{
134  
{
135  
    auto m = http::string_to_method(s);
135  
    auto m = http::string_to_method(s);
136  
    if(m != http::method::unknown)
136  
    if(m != http::method::unknown)
137  
        return priv_.verb_ == m;
137  
        return priv_.verb_ == m;
138  
    return s == priv_.verb_str_;
138  
    return s == priv_.verb_str_;
139  
}
139  
}
140  

140  

141  
//------------------------------------------------
141  
//------------------------------------------------
142  

142  

143  
capy::io_task<>
143  
capy::io_task<>
144  
route_params::
144  
route_params::
145  
send(std::string_view body)
145  
send(std::string_view body)
146  
{
146  
{
147  
    auto const sc = res.status();
147  
    auto const sc = res.status();
148  

148  

149  
    // 204 No Content / 304 Not Modified: strip headers, no body
149  
    // 204 No Content / 304 Not Modified: strip headers, no body
150  
    if(sc == status::no_content ||
150  
    if(sc == status::no_content ||
151  
       sc == status::not_modified)
151  
       sc == status::not_modified)
152  
    {
152  
    {
153  
        res.erase(field::content_type);
153  
        res.erase(field::content_type);
154  
        res.erase(field::content_length);
154  
        res.erase(field::content_length);
155  
        res.erase(field::transfer_encoding);
155  
        res.erase(field::transfer_encoding);
156  
        co_return co_await res_body.write_eof();
156  
        co_return co_await res_body.write_eof();
157  
    }
157  
    }
158  

158  

159  
    // 205 Reset Content: Content-Length=0, no body
159  
    // 205 Reset Content: Content-Length=0, no body
160  
    if(sc == status::reset_content)
160  
    if(sc == status::reset_content)
161  
    {
161  
    {
162  
        res.erase(field::transfer_encoding);
162  
        res.erase(field::transfer_encoding);
163  
        res.set_payload_size(0);
163  
        res.set_payload_size(0);
164  
        co_return co_await res_body.write_eof();
164  
        co_return co_await res_body.write_eof();
165  
    }
165  
    }
166  

166  

167  
    // Set Content-Type if not already set
167  
    // Set Content-Type if not already set
168  
    if(! res.exists(field::content_type))
168  
    if(! res.exists(field::content_type))
169  
    {
169  
    {
170  
        if(! body.empty() && body[0] == '<')
170  
        if(! body.empty() && body[0] == '<')
171  
            res.set(field::content_type,
171  
            res.set(field::content_type,
172  
                "text/html; charset=utf-8");
172  
                "text/html; charset=utf-8");
173  
        else
173  
        else
174  
            res.set(field::content_type,
174  
            res.set(field::content_type,
175  
                "text/plain; charset=utf-8");
175  
                "text/plain; charset=utf-8");
176  
    }
176  
    }
177  

177  

178  
    // Generate ETag if not already set
178  
    // Generate ETag if not already set
179  
    if(! res.exists(field::etag))
179  
    if(! res.exists(field::etag))
180  
        res.set(field::etag, etag(body));
180  
        res.set(field::etag, etag(body));
181  

181  

182  
    // Set Content-Length if not already set
182  
    // Set Content-Length if not already set
183  
    if(! res.exists(field::content_length))
183  
    if(! res.exists(field::content_length))
184  
        res.set_payload_size(body.size());
184  
        res.set_payload_size(body.size());
185  

185  

186  
    // Freshness check: auto-304 for conditional GET
186  
    // Freshness check: auto-304 for conditional GET
187  
    if(is_fresh(req, res))
187  
    if(is_fresh(req, res))
188  
    {
188  
    {
189  
        res.set_status(status::not_modified);
189  
        res.set_status(status::not_modified);
190  
        res.erase(field::content_type);
190  
        res.erase(field::content_type);
191  
        res.erase(field::content_length);
191  
        res.erase(field::content_length);
192  
        res.erase(field::transfer_encoding);
192  
        res.erase(field::transfer_encoding);
193  
        co_return co_await res_body.write_eof();
193  
        co_return co_await res_body.write_eof();
194  
    }
194  
    }
195  

195  

196  
    // HEAD: send headers only, skip body
196  
    // HEAD: send headers only, skip body
197  
    if(req.method() == method::head)
197  
    if(req.method() == method::head)
198  
    {
198  
    {
199  
        co_return co_await res_body.write_eof();
199  
        co_return co_await res_body.write_eof();
200  
    }
200  
    }
201  

201  

202  
    auto [ec, n] = co_await res_body.write_eof(
202  
    auto [ec, n] = co_await res_body.write_eof(
203  
        capy::make_buffer(body));
203  
        capy::make_buffer(body));
204  
    co_return {ec};
204  
    co_return {ec};
205  
}
205  
}
206  

206  

207  
} // http
207  
} // http
208  
} // boost
208  
} // boost