1#include "cpr/util.h"
2
3#include <algorithm>
4#include <cassert>
5#include <cctype>
6#include <chrono>
7#include <cstdint>
8#include <fstream>
9#include <iomanip>
10#include <ios>
11#include <sstream>
12#include <string>
13#include <vector>
14
15#if defined(_Win32)
16#include <Windows.h>
17#else
18// https://en.cppreference.com/w/c/string/byte/memset
19// NOLINTNEXTLINE(bugprone-reserved-identifier, cert-dcl37-c, cert-dcl51-cpp, cppcoreguidelines-macro-usage)
20#define __STDC_WANT_LIB_EXT1__ 1
21#include <cstring>
22#endif
23
24namespace cpr::util {
25
26enum class CurlHTTPCookieField : size_t {
27 Domain = 0,
28 IncludeSubdomains,
29 Path,
30 HttpsOnly,
31 Expires,
32 Name,
33 Value,
34};
35
36Cookies parseCookies(curl_slist* raw_cookies) {
37 const int CURL_HTTP_COOKIE_SIZE = static_cast<int>(CurlHTTPCookieField::Value) + 1;
38 Cookies cookies;
39 for (curl_slist* nc = raw_cookies; nc; nc = nc->next) {
40 std::vector<std::string> tokens = cpr::util::split(to_split: nc->data, delimiter: '\t');
41 while (tokens.size() < CURL_HTTP_COOKIE_SIZE) {
42 tokens.emplace_back(args: "");
43 }
44 const std::time_t expires = static_cast<time_t>(std::stoul(str: tokens.at(n: static_cast<size_t>(CurlHTTPCookieField::Expires))));
45 cookies.emplace_back(str: Cookie{
46 tokens.at(n: static_cast<size_t>(CurlHTTPCookieField::Name)),
47 tokens.at(n: static_cast<size_t>(CurlHTTPCookieField::Value)),
48 tokens.at(n: static_cast<size_t>(CurlHTTPCookieField::Domain)),
49 isTrue(s: tokens.at(n: static_cast<size_t>(CurlHTTPCookieField::IncludeSubdomains))),
50 tokens.at(n: static_cast<size_t>(CurlHTTPCookieField::Path)),
51 isTrue(s: tokens.at(n: static_cast<size_t>(CurlHTTPCookieField::HttpsOnly))),
52 std::chrono::system_clock::from_time_t(t: expires),
53 });
54 }
55 return cookies;
56}
57
58Header parseHeader(const std::string& headers, std::string* status_line, std::string* reason) {
59 Header header;
60 std::vector<std::string> lines;
61 std::istringstream stream(headers);
62 {
63 std::string line;
64 while (std::getline(in&: stream, str&: line, delim: '\n')) {
65 lines.push_back(x: line);
66 }
67 }
68
69 for (std::string& line : lines) {
70 if (line.substr(pos: 0, n: 5) == "HTTP/") {
71 // set the status_line if it was given
72 if ((status_line != nullptr) || (reason != nullptr)) {
73 line.resize(n: std::min<size_t>(a: line.size(), b: line.find_last_not_of(s: "\t\n\r ") + 1));
74 if (status_line != nullptr) {
75 *status_line = line;
76 }
77
78 // set the reason if it was given
79 if (reason != nullptr) {
80 const size_t pos1 = line.find_first_of(s: "\t ");
81 size_t pos2 = std::string::npos;
82 if (pos1 != std::string::npos) {
83 pos2 = line.find_first_of(s: "\t ", pos: pos1 + 1);
84 }
85 if (pos2 != std::string::npos) {
86 line.erase(pos: 0, n: pos2 + 1);
87 *reason = line;
88 }
89 }
90 }
91 header.clear();
92 }
93
94 if (line.length() > 0) {
95 const size_t found = line.find(c: ':');
96 if (found != std::string::npos) {
97 std::string value = line.substr(pos: found + 1);
98 value.erase(pos: 0, n: value.find_first_not_of(s: "\t "));
99 value.resize(n: std::min<size_t>(a: value.size(), b: value.find_last_not_of(s: "\t\n\r ") + 1));
100 header[line.substr(pos: 0, n: found)] = value;
101 }
102 }
103 }
104
105 return header;
106}
107
108std::vector<std::string> split(const std::string& to_split, char delimiter) {
109 std::vector<std::string> tokens;
110
111 std::stringstream stream(to_split);
112 std::string item;
113 while (std::getline(in&: stream, str&: item, delim: delimiter)) {
114 tokens.push_back(x: item);
115 }
116
117 return tokens;
118}
119
120size_t readUserFunction(char* ptr, size_t size, size_t nitems, const ReadCallback* read) {
121 size *= nitems;
122 return (*read)(ptr, size) ? size : CURL_READFUNC_ABORT;
123}
124
125size_t headerUserFunction(char* ptr, size_t size, size_t nmemb, const HeaderCallback* header) {
126 size *= nmemb;
127 return (*header)({ptr, size}) ? size : 0;
128}
129
130size_t writeFunction(char* ptr, size_t size, size_t nmemb, std::string* data) {
131 size *= nmemb;
132 data->append(s: ptr, n: size);
133 return size;
134}
135
136size_t writeFileFunction(char* ptr, size_t size, size_t nmemb, std::ofstream* file) {
137 size *= nmemb;
138 file->write(s: ptr, n: static_cast<std::streamsize>(size));
139 return size;
140}
141
142size_t writeUserFunction(char* ptr, size_t size, size_t nmemb, const WriteCallback* write) {
143 size *= nmemb;
144 return (*write)({ptr, size}) ? size : 0;
145}
146
147int debugUserFunction(CURL* /*handle*/, curl_infotype type, char* data, size_t size, const DebugCallback* debug) {
148 (*debug)(static_cast<DebugCallback::InfoType>(type), std::string(data, size));
149 return 0;
150}
151
152/**
153 * Creates a temporary CurlHolder object and uses it to escape the given string.
154 * If you plan to use this methode on a regular basis think about creating a CurlHolder
155 * object and calling urlEncode(std::string) on it.
156 *
157 * Example:
158 * CurlHolder holder;
159 * std::string input = "Hello World!";
160 * std::string result = holder.urlEncode(input);
161 **/
162std::string urlEncode(const std::string& s) {
163 const CurlHolder holder; // Create a temporary new holder for URL encoding
164 return holder.urlEncode(s);
165}
166
167/**
168 * Creates a temporary CurlHolder object and uses it to unescape the given string.
169 * If you plan to use this methode on a regular basis think about creating a CurlHolder
170 * object and calling urlDecode(std::string) on it.
171 *
172 * Example:
173 * CurlHolder holder;
174 * std::string input = "Hello%20World%21";
175 * std::string result = holder.urlDecode(input);
176 **/
177std::string urlDecode(const std::string& s) {
178 const CurlHolder holder; // Create a temporary new holder for URL decoding
179 return holder.urlDecode(s);
180}
181
182#if defined(__STDC_LIB_EXT1__)
183void secureStringClear(std::string& s) {
184 if (s.empty()) {
185 return;
186 }
187 memset_s(&s.front(), s.length(), 0, s.length());
188 s.clear();
189}
190#elif defined(_WIN32)
191void secureStringClear(std::string& s) {
192 if (s.empty()) {
193 return;
194 }
195 SecureZeroMemory(&s.front(), s.length());
196 s.clear();
197}
198#else
199#if defined(__clang__)
200#pragma clang optimize off // clang
201#elif defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW32__) || defined(__MINGW64__)
202#pragma GCC push_options // g++
203#pragma GCC optimize("O0") // g++
204#endif
205void secureStringClear(std::string& s) {
206 if (s.empty()) {
207 return;
208 }
209 // NOLINTNEXTLINE (readability-container-data-pointer)
210 char* ptr = &(s[0]);
211 memset(s: ptr, c: '\0', n: s.length());
212 s.clear();
213}
214
215#if defined(__clang__)
216#pragma clang optimize on // clang
217#elif defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW32__) || defined(__MINGW64__)
218#pragma GCC pop_options // g++
219#endif
220#endif
221
222bool isTrue(const std::string& s) {
223 std::string temp_string{s};
224 std::transform(first: temp_string.begin(), last: temp_string.end(), result: temp_string.begin(), unary_op: [](unsigned char c) { return std::tolower(c: c); });
225 return temp_string == "true";
226}
227
228} // namespace cpr::util
229