libs/http/src/server/cors.cpp

91.5% Lines (54/59) 100.0% Functions (11/11) 86.5% Branches (32/37)
libs/http/src/server/cors.cpp
Line Branch Hits 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/cors.hpp>
11 #include <utility>
12
13 namespace boost {
14 namespace http {
15
16 22 cors::
17 cors(
18 22 cors_options options) noexcept
19 22 : options_(std::move(options))
20 {
21 // VFALCO TODO Validate the strings in options against RFC
22 22 }
23
24 namespace {
25
26 struct Vary
27 {
28 52 Vary(route_params& rp)
29 52 : rp_(rp)
30 {
31 52 }
32
33 133 void set(field f, core::string_view s)
34 {
35 133 rp_.res.set(f, s);
36 133 }
37
38 17 void append(field f, core::string_view v)
39 {
40 17 auto it = rp_.res.find(f);
41
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
17 if(it != rp_.res.end())
42 {
43 std::string s = it->value;
44 s += ", ";
45 s += v;
46 rp_.res.set(it, s);
47 }
48 else
49 {
50
1/1
✓ Branch 1 taken 17 times.
17 rp_.res.set(f, v);
51 }
52 17 }
53
54 private:
55 route_params& rp_;
56 };
57
58 } // (anon)
59
60 // Access-Control-Allow-Origin
61 52 static void setOrigin(
62 Vary& v,
63 route_params const&,
64 cors_options const& options)
65 {
66
4/4
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 35 times.
✓ Branch 3 taken 36 times.
✓ Branch 4 taken 16 times.
69 if( options.origin.empty() ||
67
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 16 times.
17 options.origin == "*")
68 {
69
1/1
✓ Branch 2 taken 36 times.
36 v.set(field::access_control_allow_origin, "*");
70 36 return;
71 }
72
73
1/1
✓ Branch 1 taken 16 times.
16 v.set(
74 field::access_control_allow_origin,
75 16 options.origin);
76 16 v.append(field::vary, to_string(field::origin));
77 }
78
79 // Access-Control-Allow-Methods
80 32 static void setMethods(
81 Vary& v,
82 cors_options const& options)
83 {
84
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 24 times.
32 if(! options.methods.empty())
85 {
86
1/1
✓ Branch 1 taken 8 times.
8 v.set(
87 field::access_control_allow_methods,
88 8 options.methods);
89 8 return;
90 }
91
1/1
✓ Branch 2 taken 24 times.
24 v.set(
92 field::access_control_allow_methods,
93 "GET,HEAD,PUT,PATCH,POST,DELETE");
94 }
95
96 // Access-Control-Allow-Credentials
97 52 static void setCredentials(
98 Vary& v,
99 cors_options const& options)
100 {
101
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 16 times.
52 if(! options.credentials)
102 36 return;
103
1/1
✓ Branch 2 taken 16 times.
16 v.set(
104 field::access_control_allow_credentials,
105 "true");
106 }
107
108 // Access-Control-Allowed-Headers
109 32 static void setAllowedHeaders(
110 Vary& v,
111 route_params const& rp,
112 cors_options const& options)
113 {
114
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 24 times.
32 if(! options.allowedHeaders.empty())
115 {
116
1/1
✓ Branch 1 taken 8 times.
8 v.set(
117 field::access_control_allow_headers,
118 8 options.allowedHeaders);
119 8 return;
120 }
121 24 auto s = rp.req.value_or(
122 field::access_control_request_headers, "");
123
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 23 times.
24 if(! s.empty())
124 {
125
1/1
✓ Branch 1 taken 1 time.
1 v.set(field::access_control_allow_headers, s);
126
1/1
✓ Branch 1 taken 1 time.
1 v.append(field::vary, s);
127 }
128 }
129
130 // Access-Control-Expose-Headers
131 52 static void setExposeHeaders(
132 Vary& v,
133 cors_options const& options)
134 {
135
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 16 times.
52 if(options.exposedHeaders.empty())
136 36 return;
137
1/1
✓ Branch 1 taken 16 times.
16 v.set(
138 field::access_control_expose_headers,
139 16 options.exposedHeaders);
140 }
141
142 // Access-Control-Max-Age
143 32 static void setMaxAge(
144 Vary& v,
145 cors_options const& options)
146 {
147
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 8 times.
32 if(options.max_age.count() == 0)
148 24 return;
149
1/1
✓ Branch 2 taken 8 times.
8 v.set(
150 field::access_control_max_age,
151
1/1
✓ Branch 2 taken 8 times.
16 std::to_string(
152 options.max_age.count()));
153 }
154
155 route_task
156
1/1
✓ Branch 1 taken 52 times.
52 cors::
157 operator()(
158 route_params& rp) const
159 {
160 Vary v(rp);
161 if(rp.req.method() == method::options)
162 {
163 // preflight
164 setOrigin(v, rp, options_);
165 setMethods(v, options_);
166 setCredentials(v, options_);
167 setAllowedHeaders(v, rp, options_);
168 setMaxAge(v, options_);
169 setExposeHeaders(v, options_);
170
171 if(options_.preFlightContinue)
172 co_return route_next;
173
174 // Safari and others need this for 204 or may hang
175 rp.res.set_status(options_.result);
176 auto [ec] = co_await rp.send("");
177 if(ec)
178 co_return route_error(ec);
179 co_return route_done;
180 }
181
182 // actual response
183 setOrigin(v, rp, options_);
184 setCredentials(v, options_);
185 setExposeHeaders(v, options_);
186 co_return route_next;
187 104 }
188
189 } // http
190 } // boost
191