src/detail/util.cpp

0.0% Lines (0/60) 0.0% List of functions (0/7) 0.0% Branches (0/113)
util.cpp
f(x) Functions (7)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Mohammad Nejati
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/beast2
8 //
9
10 #include "util.hpp"
11
12 #include <boost/burl/response.hpp>
13 #include <boost/http/field.hpp>
14 #include <boost/url.hpp>
15
16 namespace boost
17 {
18 namespace burl
19 {
20 namespace detail
21 {
22
23 namespace grammar = boost::urls::grammar;
24 namespace variant2 = boost::variant2;
25 namespace fs = std::filesystem;
26
27 namespace
28 {
29 struct attr_char_t
30 {
31 constexpr bool
32 operator()(char c) const noexcept
33 {
34 // clang-format off
35 return grammar::alnum_chars(c) ||
36 c == '!' || c == '#' || c == '$' || c == '&' || c == '+' ||
37 c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
38 c == '|' || c == '~';
39 // clang-format on
40 }
41 };
42
43 constexpr attr_char_t attr_char{};
44
45 struct value_char_t
46 {
47 constexpr bool
48 operator()(char c) const noexcept
49 {
50 return attr_char(c) || c == '%';
51 }
52 };
53
54 constexpr auto value_char = value_char_t{};
55
56 struct quoted_string_t
57 {
58 using value_type = core::string_view;
59
60 constexpr boost::system::result<value_type>
61 parse(char const*& it, char const* end) const noexcept
62 {
63 const auto it0 = it;
64
65 if(it == end)
66 return grammar::error::need_more;
67
68 if(*it++ != '"')
69 return grammar::error::mismatch;
70
71 for(; it != end && *it != '"'; it++)
72 {
73 if(*it == '\\')
74 it++;
75 }
76
77 if(*it != '"')
78 return grammar::error::mismatch;
79
80 return value_type(it0, ++it);
81 }
82 };
83
84 constexpr auto quoted_string = quoted_string_t{};
85
86 std::string
87 unquote_string(core::string_view sv)
88 {
89 sv.remove_prefix(1);
90 sv.remove_suffix(1);
91
92 auto rs = std::string{};
93 for(auto it = sv.begin(); it != sv.end(); it++)
94 {
95 if(*it == '\\')
96 it++;
97 rs.push_back(*it);
98 }
99 return rs;
100 }
101 } // namespace
102
103 std::optional<std::string>
104 extract_filename_form_content_disposition(std::string_view sv)
105 {
106 static constexpr auto parser = grammar::range_rule(
107 grammar::tuple_rule(
108 grammar::squelch(grammar::optional_rule(grammar::delim_rule(';'))),
109 grammar::squelch(grammar::optional_rule(grammar::delim_rule(' '))),
110 grammar::token_rule(attr_char),
111 grammar::squelch(grammar::optional_rule(grammar::delim_rule('='))),
112 grammar::optional_rule(
113 grammar::variant_rule(
114 quoted_string, grammar::token_rule(value_char)))));
115
116 const auto parse_rs = grammar::parse(sv, parser);
117
118 if(parse_rs.has_error())
119 return std::nullopt;
120
121 for(auto&& [name, value] : parse_rs.value())
122 {
123 if(name == "filename" && value.has_value())
124 {
125 if(value->index() == 0)
126 {
127 return unquote_string(variant2::get<0>(value.value()));
128 }
129 else
130 {
131 return std::string{ variant2::get<1>(value.value()) };
132 }
133 }
134 }
135
136 return std::nullopt;
137 }
138
139 fs::path
140 resolve_dest(response& resp, fs::path dest)
141 {
142 auto const sanitize_filename = [](fs::path path) -> fs::path
143 {
144 auto name = path.filename();
145 if(name.empty() || name == "." || name == "..")
146 return {};
147 return name;
148 };
149
150 std::error_code ec;
151 if(!std::filesystem::is_directory(dest, ec))
152 return dest;
153
154 for(auto sv : resp.headers().find_all(http::field::content_disposition))
155 {
156 if(auto filename = extract_filename_form_content_disposition(sv))
157 {
158 if(auto name = sanitize_filename(*filename); !name.empty())
159 return dest / name;
160 }
161 }
162
163 auto segs = resp.url().segments();
164 if(!segs.empty())
165 {
166 if(auto name = sanitize_filename(segs.back()); !name.empty())
167 return dest / name;
168 }
169
170 return dest / "index.html";
171 }
172
173 } // namespace detail
174 } // namespace burl
175 } // namespace boost
176