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/cors.hpp>
10  
#include <boost/http/server/cors.hpp>
11  
#include <utility>
11  
#include <utility>
12  

12  

13  
namespace boost {
13  
namespace boost {
14  
namespace http {
14  
namespace http {
15  

15  

16  
cors::
16  
cors::
17  
cors(
17  
cors(
18  
    cors_options options) noexcept
18  
    cors_options options) noexcept
19  
    : options_(std::move(options))
19  
    : options_(std::move(options))
20  
{
20  
{
21  
    // VFALCO TODO Validate the strings in options against RFC
21  
    // VFALCO TODO Validate the strings in options against RFC
22  
}
22  
}
23  

23  

24  
namespace {
24  
namespace {
25  

25  

26  
struct Vary
26  
struct Vary
27  
{
27  
{
28  
    Vary(route_params& rp)
28  
    Vary(route_params& rp)
29  
        : rp_(rp)
29  
        : rp_(rp)
30  
    {
30  
    {
31  
    }
31  
    }
32  

32  

33  
    void set(field f, core::string_view s)
33  
    void set(field f, core::string_view s)
34  
    {
34  
    {
35  
        rp_.res.set(f, s);
35  
        rp_.res.set(f, s);
36  
    }
36  
    }
37  

37  

38  
    void append(field f, core::string_view v)
38  
    void append(field f, core::string_view v)
39  
    {
39  
    {
40  
        auto it = rp_.res.find(f);
40  
        auto it = rp_.res.find(f);
41  
        if(it != rp_.res.end())
41  
        if(it != rp_.res.end())
42  
        {
42  
        {
43  
            std::string s = it->value;
43  
            std::string s = it->value;
44  
            s += ", ";
44  
            s += ", ";
45  
            s += v;
45  
            s += v;
46  
            rp_.res.set(it, s);
46  
            rp_.res.set(it, s);
47  
        }
47  
        }
48  
        else
48  
        else
49  
        {
49  
        {
50  
            rp_.res.set(f, v);
50  
            rp_.res.set(f, v);
51  
        }
51  
        }
52  
    }
52  
    }
53  

53  

54  
private:
54  
private:
55  
    route_params& rp_;
55  
    route_params& rp_;
56  
};
56  
};
57  

57  

58  
} // (anon)
58  
} // (anon)
59  

59  

60  
// Access-Control-Allow-Origin
60  
// Access-Control-Allow-Origin
61  
static void setOrigin(
61  
static void setOrigin(
62  
    Vary& v,
62  
    Vary& v,
63  
    route_params const&,
63  
    route_params const&,
64  
    cors_options const& options)
64  
    cors_options const& options)
65  
{
65  
{
66  
    if( options.origin.empty() ||
66  
    if( options.origin.empty() ||
67  
        options.origin == "*")
67  
        options.origin == "*")
68  
    {
68  
    {
69  
        v.set(field::access_control_allow_origin, "*");
69  
        v.set(field::access_control_allow_origin, "*");
70  
        return;
70  
        return;
71  
    }
71  
    }
72  

72  

73  
    v.set(
73  
    v.set(
74  
        field::access_control_allow_origin,
74  
        field::access_control_allow_origin,
75  
        options.origin);
75  
        options.origin);
76  
    v.append(field::vary, to_string(field::origin));
76  
    v.append(field::vary, to_string(field::origin));
77  
}
77  
}
78  

78  

79  
// Access-Control-Allow-Methods
79  
// Access-Control-Allow-Methods
80  
static void setMethods(
80  
static void setMethods(
81  
    Vary& v,
81  
    Vary& v,
82  
    cors_options const& options)
82  
    cors_options const& options)
83  
{
83  
{
84  
    if(! options.methods.empty())
84  
    if(! options.methods.empty())
85  
    {
85  
    {
86  
        v.set(
86  
        v.set(
87  
            field::access_control_allow_methods,
87  
            field::access_control_allow_methods,
88  
            options.methods);
88  
            options.methods);
89  
        return;
89  
        return;
90  
    }
90  
    }
91  
    v.set(
91  
    v.set(
92  
        field::access_control_allow_methods,
92  
        field::access_control_allow_methods,
93  
        "GET,HEAD,PUT,PATCH,POST,DELETE");
93  
        "GET,HEAD,PUT,PATCH,POST,DELETE");
94  
}
94  
}
95  

95  

96  
// Access-Control-Allow-Credentials
96  
// Access-Control-Allow-Credentials
97  
static void setCredentials(
97  
static void setCredentials(
98  
    Vary& v,
98  
    Vary& v,
99  
    cors_options const& options)
99  
    cors_options const& options)
100  
{
100  
{
101  
    if(! options.credentials)
101  
    if(! options.credentials)
102  
        return;
102  
        return;
103  
    v.set(
103  
    v.set(
104  
        field::access_control_allow_credentials,
104  
        field::access_control_allow_credentials,
105  
        "true");
105  
        "true");
106  
}
106  
}
107  

107  

108  
// Access-Control-Allowed-Headers
108  
// Access-Control-Allowed-Headers
109  
static void setAllowedHeaders(
109  
static void setAllowedHeaders(
110  
    Vary& v,
110  
    Vary& v,
111  
    route_params const& rp,
111  
    route_params const& rp,
112  
    cors_options const& options)
112  
    cors_options const& options)
113  
{
113  
{
114  
    if(! options.allowedHeaders.empty())
114  
    if(! options.allowedHeaders.empty())
115  
    {
115  
    {
116  
        v.set(
116  
        v.set(
117  
            field::access_control_allow_headers,
117  
            field::access_control_allow_headers,
118  
            options.allowedHeaders);
118  
            options.allowedHeaders);
119  
        return;
119  
        return;
120  
    }
120  
    }
121  
    auto s = rp.req.value_or(
121  
    auto s = rp.req.value_or(
122  
        field::access_control_request_headers, "");
122  
        field::access_control_request_headers, "");
123  
    if(! s.empty())
123  
    if(! s.empty())
124  
    {
124  
    {
125  
        v.set(field::access_control_allow_headers, s);
125  
        v.set(field::access_control_allow_headers, s);
126  
        v.append(field::vary, s);
126  
        v.append(field::vary, s);
127  
    }
127  
    }
128  
}
128  
}
129  

129  

130  
// Access-Control-Expose-Headers
130  
// Access-Control-Expose-Headers
131  
static void setExposeHeaders(
131  
static void setExposeHeaders(
132  
    Vary& v,
132  
    Vary& v,
133  
    cors_options const& options)
133  
    cors_options const& options)
134  
{
134  
{
135  
    if(options.exposedHeaders.empty())
135  
    if(options.exposedHeaders.empty())
136  
        return;
136  
        return;
137  
    v.set(
137  
    v.set(
138  
        field::access_control_expose_headers,
138  
        field::access_control_expose_headers,
139  
        options.exposedHeaders);
139  
        options.exposedHeaders);
140  
}
140  
}
141  

141  

142  
// Access-Control-Max-Age
142  
// Access-Control-Max-Age
143  
static void setMaxAge(
143  
static void setMaxAge(
144  
    Vary& v,
144  
    Vary& v,
145  
    cors_options const& options)
145  
    cors_options const& options)
146  
{
146  
{
147  
    if(options.max_age.count() == 0)
147  
    if(options.max_age.count() == 0)
148  
        return;
148  
        return;
149  
    v.set(
149  
    v.set(
150  
        field::access_control_max_age,
150  
        field::access_control_max_age,
151  
        std::to_string(
151  
        std::to_string(
152  
            options.max_age.count()));
152  
            options.max_age.count()));
153  
}
153  
}
154  

154  

155  
route_task
155  
route_task
156  
cors::
156  
cors::
157  
operator()(
157  
operator()(
158  
    route_params& rp) const
158  
    route_params& rp) const
159  
{
159  
{
160  
    Vary v(rp);
160  
    Vary v(rp);
161  
    if(rp.req.method() == method::options)
161  
    if(rp.req.method() == method::options)
162  
    {
162  
    {
163  
        // preflight
163  
        // preflight
164  
        setOrigin(v, rp, options_);
164  
        setOrigin(v, rp, options_);
165  
        setMethods(v, options_);
165  
        setMethods(v, options_);
166  
        setCredentials(v, options_);
166  
        setCredentials(v, options_);
167  
        setAllowedHeaders(v, rp, options_);
167  
        setAllowedHeaders(v, rp, options_);
168  
        setMaxAge(v, options_);
168  
        setMaxAge(v, options_);
169  
        setExposeHeaders(v, options_);
169  
        setExposeHeaders(v, options_);
170  

170  

171  
        if(options_.preFlightContinue)
171  
        if(options_.preFlightContinue)
172  
            co_return route_next;
172  
            co_return route_next;
173  

173  

174  
        // Safari and others need this for 204 or may hang
174  
        // Safari and others need this for 204 or may hang
175  
        rp.res.set_status(options_.result);
175  
        rp.res.set_status(options_.result);
176  
        auto [ec] = co_await rp.send("");
176  
        auto [ec] = co_await rp.send("");
177  
        if(ec)
177  
        if(ec)
178  
            co_return route_error(ec);
178  
            co_return route_error(ec);
179  
        co_return route_done;
179  
        co_return route_done;
180  
    }
180  
    }
181  

181  

182  
    // actual response
182  
    // actual response
183  
    setOrigin(v, rp, options_);
183  
    setOrigin(v, rp, options_);
184  
    setCredentials(v, options_);
184  
    setCredentials(v, options_);
185  
    setExposeHeaders(v, options_);
185  
    setExposeHeaders(v, options_);
186  
    co_return route_next;
186  
    co_return route_next;
187  
}
187  
}
188  

188  

189  
} // http
189  
} // http
190  
} // boost
190  
} // boost