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#ifndef DOXYGEN_SHOULD_SKIP_THIS
21
22#include "web/http/http_utils.h"
23
24#include "utils/hexdump.h"
25#include "utils/system/time.h"
26#include "web/http/CiStringMap.h"
27#include "web/http/CookieOptions.h"
28
29#include <algorithm>
30#include <cctype>
31#include <cerrno>
32#include <iomanip>
33#include <sstream>
34#include <sys/stat.h>
35
36#endif /* DOXYGEN_SHOULD_SKIP_THIS */
37
38namespace httputils {
39
40 static int from_hex(int ch) {
41 return isdigit(ch) != 0 ? ch - '0' : tolower(ch) - 'a' + 10;
42 }
43
44 std::string url_decode(const std::string& text) {
45 std::string escaped;
46
47 for (std::string::const_iterator i = text.begin(), n = text.end(); i != n; ++i) {
48 const std::string::value_type c = (*i);
49
50 if (c == '%') {
51 if ((i[1] != 0) && (i[2] != 0)) {
52 escaped += static_cast<char>(from_hex(i[1]) << 4 | from_hex(i[2]));
53 i += 2;
54 }
55 } else if (c == '+') {
56 escaped += ' ';
57 } else {
58 escaped += c;
59 }
60 }
61
62 return escaped;
63 }
64
65 std::string url_encode(const std::string& text) {
66 std::ostringstream escaped;
67 escaped.fill('0'); // cppcheck-suppress ignoredReturnValue
68 escaped << std::hex;
69
70 for (const char c : text) {
71 if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~') {
72 escaped << c;
73 } else {
74 escaped << std::uppercase;
75 escaped << '%' << std::setw(2) << static_cast<unsigned char>(c);
76 escaped << std::nouppercase;
77 }
78 }
79
80 return escaped.str();
81 }
82
83 std::string& str_trimm(std::string& text) {
84 text.erase(text.find_last_not_of(" \t") + 1);
85 text.erase(0, text.find_first_not_of(" \t"));
86
87 return text;
88 }
89
90 std::pair<std::string, std::string> str_split(const std::string& base, char c_middle) {
91 std::pair<std::string, std::string> split;
92
93 const unsigned long middle = base.find_first_of(c_middle);
94
95 split.first = base.substr(0, middle);
96
97 if (middle != std::string::npos) {
98 split.second = base.substr(middle + 1);
99 }
100
101 return split;
102 }
103
104 std::pair<std::string, std::string> str_split_last(const std::string& base, char c_middle) {
105 std::pair<std::string, std::string> split;
106
107 const unsigned long middle = base.find_last_of(c_middle);
108
109 split.first = base.substr(0, middle);
110
111 if (middle != std::string::npos) {
112 split.second = base.substr(middle + 1);
113 }
114
115 return split;
116 }
117
118 std::string to_http_date(struct tm* tm) {
119 char buf[100];
120
121 if (tm == nullptr) {
122 const time_t now = utils::system::time(nullptr);
123 tm = utils::system::gmtime(&now);
124 } else {
125 const time_t time = utils::system::mktime(tm);
126 tm = utils::system::gmtime(&time);
127 }
128
129 (void) strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S %Z", tm);
130 errno = 0; // Errno can be modified by strftime even though there is no error
131
132 return std::string(buf);
133 }
134
135 struct tm from_http_date(const std::string& http_date) {
136 struct tm tm{};
137
138 strptime(http_date.c_str(), "%a, %d %b %Y %H:%M:%S", &tm);
139 tm.tm_zone = "GMT";
140
141 return tm;
142 }
143
144 std::string file_mod_http_date(const std::string& filePath) {
145 char buf[100];
146
147 struct stat attrib{};
148 stat(filePath.c_str(), &attrib); // TODO: to core::system
149
150 (void) strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S %Z", gmtime(&(attrib.st_mtime))); // TODO: to core::system
151 errno = 0; // Errno can be modified by strftime even though there is no error
152
153 return std::string(buf);
154 }
155
157 return std::transform(string.begin(), string.end(), string.begin(), ::tolower);
158 }
159
160 std::string toString(const std::string& method,
161 const std::string& url,
162 const std::string& version,
163 const web::http::CiStringMap<std::string>& queries,
164 const web::http::CiStringMap<std::string>& header,
165 const web::http::CiStringMap<std::string>& cookies,
166 const std::vector<char>& body) {
167 const int prefixLength = 9;
168 int keyLength = 0;
169
170 for (const auto& [key, value] : queries) {
171 keyLength = std::max(keyLength, static_cast<int>(key.size()));
172 }
173 for (const auto& [key, value] : header) {
174 keyLength = std::max(keyLength, static_cast<int>(key.size()));
175 }
176 for (const auto& [key, value] : cookies) {
177 keyLength = std::max(keyLength, static_cast<int>(key.size()));
178 }
179
180 std::stringstream requestStream;
181
182 requestStream << std::setw(prefixLength) << "Request"
183 << ": " << std::setw(keyLength) << "Method"
184 << " : " << method << "\n";
185 requestStream << std::setw(prefixLength) << ""
186 << ": " << std::setw(keyLength) << "Url"
187 << " : " << url << "\n";
188 requestStream << std::setw(prefixLength) << ""
189 << ": " << std::setw(keyLength) << "Version"
190 << " : " << version << "\n";
191
192 std::string prefix;
193
194 if (!queries.empty()) {
195 prefix = "Queries";
196 for (const auto& [query, value] : queries) {
197 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << query << " : " << value << "\n";
198 prefix = "";
199 }
200 }
201
202 if (!header.empty()) {
203 prefix = "Header";
204 for (const auto& [field, value] : header) {
205 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << field << " : " << value << "\n";
206 prefix = "";
207 }
208 }
209
210 if (!cookies.empty()) {
211 prefix = "Cookies";
212 for (const auto& [cookie, value] : cookies) {
213 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << cookie << " : " << value << "\n";
214 prefix = "";
215 }
216 }
217
218 if (!body.empty()) {
219 prefix = "Body";
220 requestStream << std::setw(prefixLength) << prefix << utils::hexDump(body, prefixLength) << "\n";
221 }
222
223 std::string string = requestStream.str();
224 if (!string.empty()) {
225 string.pop_back();
226 }
227
228 return string;
229 }
230
231 std::string toString(const std::string& version,
232 const std::string& statusCode,
233 const std::string& reason,
234 const web::http::CiStringMap<std::string>& header,
235 const web::http::CiStringMap<web::http::CookieOptions>& cookies,
236 const std::vector<char>& body) {
237 const int prefixLength = 9;
238 int keyLength = 0;
239
240 for (const auto& [key, value] : header) {
241 keyLength = std::max(keyLength, static_cast<int>(key.size()));
242 }
243 for (const auto& [key, value] : cookies) {
244 keyLength = std::max(keyLength, static_cast<int>(key.size()));
245 }
246
247 std::stringstream requestStream;
248
249 requestStream << std::setw(prefixLength) << "Response"
250 << ": " << std::setw(keyLength) << "Version"
251 << " : " << version << "\n";
252 requestStream << std::setw(prefixLength) << ""
253 << ": " << std::setw(keyLength) << "Status"
254 << " : " << statusCode << "\n";
255 requestStream << std::setw(prefixLength) << ""
256 << ": " << std::setw(keyLength) << "Reason"
257 << " : " << reason << "\n";
258
259 std::string prefix;
260
261 if (!header.empty()) {
262 prefix = "Header";
263 for (const auto& [field, value] : header) {
264 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << field << " : " << value << "\n";
265 prefix = "";
266 }
267 }
268
269 if (!cookies.empty()) {
270 prefix = "Cookies";
271 for (const auto& [cookie, options] : cookies) {
272 requestStream << std::setw(prefixLength) << prefix << ": " << std::setw(keyLength) << cookie << " : " << options.getValue()
273 << "\n";
274 for (const auto& [optionKey, optionValue] : options.getOptions()) {
275 requestStream << std::setw(prefixLength) << ""
276 << ":" << std::setw(keyLength) << ""
277 << " : " << optionKey << "=" << optionValue << "\n";
278 }
279 prefix = "";
280 }
281 }
282
283 if (!body.empty()) {
284 prefix = "Body";
285 requestStream << std::setw(prefixLength) << prefix << utils::hexDump(body, prefixLength) << "\n";
286 }
287
288 std::string string = requestStream.str();
289 if (!string.empty()) {
290 string.pop_back();
291 }
292
293 return string;
294 }
295
296} // namespace httputils
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:40
time_t time(time_t *tloc)
Definition time.cpp:30