SNode.C
Loading...
Searching...
No Matches
http_utils.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#ifndef DOXYGEN_SHOULD_SKIP_THIS
43
44#include "web/http/http_utils.h"
45
46#include "utils/hexdump.h"
47#include "utils/system/time.h"
48#include "web/http/CiStringMap.h"
49#include "web/http/CookieOptions.h"
50
51#include <algorithm>
52#include <cctype>
53#include <cerrno>
54#include <iomanip>
55#include <sstream>
56#include <sys/stat.h>
57
58#endif /* DOXYGEN_SHOULD_SKIP_THIS */
59
60namespace httputils {
61
62 static int from_hex(int ch) {
63 return isdigit(ch) != 0 ? ch - '0' : tolower(ch) - 'a' + 10;
64 }
65
66 std::string url_decode(const std::string& text) {
67 std::string escaped;
68
69 for (std::string::const_iterator i = text.begin(), n = text.end(); i != n; ++i) {
70 const std::string::value_type c = (*i);
71
72 if (c == '%') {
73 if ((i[1] != 0) && (i[2] != 0)) {
74 escaped += static_cast<char>(from_hex(i[1]) << 4 | from_hex(i[2]));
75 i += 2;
76 }
77 } else if (c == '+') {
78 escaped += ' ';
79 } else {
80 escaped += c;
81 }
82 }
83
84 return escaped;
85 }
86
87 std::string url_encode(const std::string& text) {
88 std::ostringstream escaped;
89 escaped.fill('0'); // cppcheck-suppress ignoredReturnValue
90 escaped << std::hex;
91
92 for (const char c : text) {
93 if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~') {
94 escaped << c;
95 } else {
96 escaped << std::uppercase;
97 escaped << '%' << std::setw(2) << static_cast<unsigned char>(c);
98 escaped << std::nouppercase;
99 }
100 }
101
102 return escaped.str();
103 }
104
105 std::string& str_trimm(std::string& text) {
106 text.erase(text.find_last_not_of(" \t") + 1);
107 text.erase(0, text.find_first_not_of(" \t"));
108
109 return text;
110 }
111
112 std::pair<std::string, std::string> str_split(const std::string& base, char c_middle) {
113 std::pair<std::string, std::string> split;
114
115 const unsigned long middle = base.find_first_of(c_middle);
116
117 split.first = base.substr(0, middle);
118
119 if (middle != std::string::npos) {
120 split.second = base.substr(middle + 1);
121 }
122
123 return split;
124 }
125
126 std::pair<std::string, std::string> str_split_last(const std::string& base, char c_middle) {
127 std::pair<std::string, std::string> split;
128
129 const unsigned long middle = base.find_last_of(c_middle);
130
131 split.first = base.substr(0, middle);
132
133 if (middle != std::string::npos) {
134 split.second = base.substr(middle + 1);
135 }
136
137 return split;
138 }
139
140 std::string to_http_date(struct tm* tm) {
141 char buf[100];
142
143 if (tm == nullptr) {
144 const time_t now = utils::system::time(nullptr);
145 tm = utils::system::gmtime(&now);
146 } else {
147 const time_t time = utils::system::mktime(tm);
148 tm = utils::system::gmtime(&time);
149 }
150
151 (void) strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S %Z", tm);
152 errno = 0; // Errno can be modified by strftime even though there is no error
153
154 return std::string(buf);
155 }
156
157 struct tm from_http_date(const std::string& http_date) {
158 struct tm tm{};
159
160 strptime(http_date.c_str(), "%a, %d %b %Y %H:%M:%S", &tm);
161 tm.tm_zone = "GMT";
162
163 return tm;
164 }
165
166 std::string file_mod_http_date(const std::string& filePath) {
167 char buf[100];
168
169 struct stat attrib{};
170 stat(filePath.c_str(), &attrib); // TODO: to core::system
171
172 (void) strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S %Z", gmtime(&(attrib.st_mtime))); // TODO: to core::system
173 errno = 0; // Errno can be modified by strftime even though there is no error
174
175 return std::string(buf);
176 }
177
178 std::string::iterator to_lower(std::string& string) {
179 return std::transform(string.begin(), string.end(), string.begin(), ::tolower);
180 }
181
182 std::string toString(const std::string& method,
183 const std::string& url,
184 const std::string& version,
185 const web::http::CiStringMap<std::string>& queries,
186 const web::http::CiStringMap<std::string>& header,
187 const web::http::CiStringMap<std::string>& cookies,
188 const std::vector<char>& body) {
189 const int prefixLength = 9;
190 int keyLength = 0;
191
192 for (const auto& [key, value] : queries) {
193 keyLength = std::max(keyLength, static_cast<int>(key.size()));
194 }
195 for (const auto& [key, value] : header) {
196 keyLength = std::max(keyLength, static_cast<int>(key.size()));
197 }
198 for (const auto& [key, value] : cookies) {
199 keyLength = std::max(keyLength, static_cast<int>(key.size()));
200 }
201
202 std::stringstream requestStream;
203
204 requestStream << std::setw(prefixLength) << "Request"
205 << ": " << std::setw(keyLength) << "Method"
206 << " : " << method << "\n";
207 requestStream << std::setw(prefixLength) << ""
208 << ": " << std::setw(keyLength) << "Url"
209 << " : " << url << "\n";
210 requestStream << std::setw(prefixLength) << ""
211 << ": " << std::setw(keyLength) << "Version"
212 << " : " << version << "\n";
213
214 std::string prefix;
215
216 if (!queries.empty()) {
217 prefix = "Queries";
218 for (const auto& [query, value] : queries) {
219 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << query << " : " << value << "\n";
220 prefix = "";
221 }
222 }
223
224 if (!header.empty()) {
225 prefix = "Header";
226 for (const auto& [field, value] : header) {
227 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << field << " : " << value << "\n";
228 prefix = "";
229 }
230 }
231
232 if (!cookies.empty()) {
233 prefix = "Cookies";
234 for (const auto& [cookie, value] : cookies) {
235 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << cookie << " : " << value << "\n";
236 prefix = "";
237 }
238 }
239
240 if (!body.empty()) {
241 prefix = "Body";
242 requestStream << std::setw(prefixLength) << prefix << utils::hexDump(body, prefixLength) << "\n";
243 }
244
245 std::string string = requestStream.str();
246 if (!string.empty()) {
247 string.pop_back();
248 }
249
250 return string;
251 }
252
253 std::string toString(const std::string& version,
254 const std::string& statusCode,
255 const std::string& reason,
256 const web::http::CiStringMap<std::string>& header,
257 const web::http::CiStringMap<web::http::CookieOptions>& cookies,
258 const std::vector<char>& body) {
259 const int prefixLength = 9;
260 int keyLength = 0;
261
262 for (const auto& [key, value] : header) {
263 keyLength = std::max(keyLength, static_cast<int>(key.size()));
264 }
265 for (const auto& [key, value] : cookies) {
266 keyLength = std::max(keyLength, static_cast<int>(key.size()));
267 }
268
269 std::stringstream requestStream;
270
271 requestStream << std::setw(prefixLength) << "Response"
272 << ": " << std::setw(keyLength) << "Version"
273 << " : " << version << "\n";
274 requestStream << std::setw(prefixLength) << ""
275 << ": " << std::setw(keyLength) << "Status"
276 << " : " << statusCode << "\n";
277 requestStream << std::setw(prefixLength) << ""
278 << ": " << std::setw(keyLength) << "Reason"
279 << " : " << reason << "\n";
280
281 std::string prefix;
282
283 if (!header.empty()) {
284 prefix = "Header";
285 for (const auto& [field, value] : header) {
286 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << field << " : " << value << "\n";
287 prefix = "";
288 }
289 }
290
291 if (!cookies.empty()) {
292 prefix = "Cookies";
293 for (const auto& [cookie, options] : cookies) {
294 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << cookie << " : " << options.getValue()
295 << "\n";
296 for (const auto& [optionKey, optionValue] : options.getOptions()) {
297 requestStream << std::setw(prefixLength) << ""
298 << ":" << std::setw(keyLength) << ""
299 << " : " << optionKey << "=" << optionValue << "\n";
300 }
301 prefix = "";
302 }
303 }
304
305 if (!body.empty()) {
306 prefix = "Body";
307 requestStream << std::setw(prefixLength) << prefix << utils::hexDump(body, prefixLength) << "\n";
308 }
309
310 std::string string = requestStream.str();
311 if (!string.empty()) {
312 string.pop_back();
313 }
314
315 return string;
316 }
317
318} // namespace httputils
const std::map< std::string, std::string > & getOptions() const
const std::string & getValue() const
std::string to_http_date(struct tm *tm)
std::string toString(const std::string &version, const std::string &statusCode, const std::string &reason, const web::http::CiStringMap< std::string > &header, const web::http::CiStringMap< web::http::CookieOptions > &cookies, const std::vector< char > &body)
static int from_hex(int ch)
std::string url_encode(const std::string &text)
std::string file_mod_http_date(const std::string &filePath)
std::string::iterator to_lower(std::string &string)
std::pair< std::string, std::string > str_split_last(const std::string &base, char c_middle)
std::string url_decode(const std::string &text)
std::pair< std::string, std::string > str_split(const std::string &base, char c_middle)
std::string & str_trimm(std::string &text)
std::string toString(const std::string &method, const std::string &url, const std::string &version, const web::http::CiStringMap< std::string > &queries, const web::http::CiStringMap< std::string > &header, const web::http::CiStringMap< std::string > &cookies, const std::vector< char > &body)
struct tm * gmtime(const time_t *timep)
Definition time.cpp:62
time_t time(time_t *tloc)
Definition time.cpp:52
std::string hexDump(const std::vector< char > &bytes, int prefixLength, bool prefixAtFirstLine)
Definition hexdump.cpp:58