1 #include "coeurl/request.hpp"
2
3 #include "coeurl/client.hpp"
4
5 // for TCP_MAXRT
6 #if __has_include(<winsock2.h>)
7 #include <winsock2.h>
8 #endif
9
10 // for TCP_USER_TIMEOUT
11 #if __has_include(<netinet/tcp.h>)
12 #include <netinet/tcp.h>
13 #endif
14
15 namespace coeurl {
16 /* CURLOPT_WRITEFUNCTION */
write_cb(void * ptr,size_t size,size_t nmemb,void * data)17 size_t Request::write_cb(void *ptr, size_t size, size_t nmemb, void *data) {
18 Request *request = (Request *)data;
19 Client::log->trace("Write: {} ({})", request->url_, nmemb);
20 request->response_.insert(request->response_.end(), (uint8_t *)ptr, (uint8_t *)ptr + nmemb);
21
22 return size * nmemb;
23 }
24
25 /* CURLOPT_WRITEFUNCTION */
read_cb(char * buffer,size_t size,size_t nitems,void * data)26 size_t Request::read_cb(char *buffer, size_t size, size_t nitems, void *data) {
27 Request *request = (Request *)data;
28
29 size_t data_left = request->request_.size() - request->readoffset;
30
31 auto data_to_copy = std::min(data_left, nitems * size);
32
33 Client::log->trace("Read: {} ({})", request->url_, data_to_copy);
34
35 if (data_to_copy) {
36 auto read_ptr = request->request_.data() + request->readoffset;
37 Client::log->trace("Copying: {}", std::string_view(read_ptr, data_to_copy));
38 std::copy(read_ptr, read_ptr + data_to_copy, buffer);
39 Client::log->trace("Copied: {}", std::string_view(buffer, data_to_copy));
40
41 request->readoffset += data_to_copy;
42 }
43
44 return data_to_copy;
45 }
46
trim(std::string_view val)47 static std::string_view trim(std::string_view val) {
48 while (val.size() && isspace(val.front()))
49 val.remove_prefix(1);
50 while (val.size() && isspace(val.back()))
51 val.remove_suffix(1);
52
53 return val;
54 }
55
ascii_lower(char c)56 static char ascii_lower(char c) {
57 if (c >= 'A' && c <= 'Z')
58 c |= 0b00100000;
59 return c;
60 }
61
operator ()(const std::string & a,const std::string & b) const62 bool header_less::operator()(const std::string &a, const std::string &b) const {
63 if (a.size() != b.size())
64 return a.size() < b.size();
65
66 for (size_t i = 0; i < a.size(); i++) {
67 auto a_c = ascii_lower(a[i]);
68 auto b_c = ascii_lower(b[i]);
69
70 if (a_c != b_c)
71 return a_c < b_c;
72 }
73
74 return false;
75 }
76
77 /* CURLOPT_HEADERFUNCTION */
header_cb(char * buffer,size_t,size_t nitems,void * data)78 size_t Request::header_cb(char *buffer, size_t, size_t nitems, void *data) {
79 Request *request = (Request *)data;
80 std::string_view header(buffer, nitems);
81
82 if (auto pos = header.find(':'); pos != std::string_view::npos) {
83 auto key = trim(header.substr(0, pos));
84 auto val = trim(header.substr(pos + 1));
85
86 Client::log->debug("Header: {} ({}: {})", request->url_, key, val);
87
88 request->response_headers_.insert({std::string(key), std::string(val)});
89 }
90
91 return nitems;
92 }
93
94 /* CURLOPT_PROGRESSFUNCTION */
prog_cb(void * p,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ult,curl_off_t uln)95 int Request::prog_cb(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ult, curl_off_t uln) {
96 Request *r = (Request *)p;
97 (void)ult;
98 (void)uln;
99
100 if (r->on_upload_progress_)
101 r->on_upload_progress_(uln, ult);
102 if (r->on_download_progess_)
103 r->on_download_progess_(dlnow, dltotal);
104
105 Client::log->trace("Progress: {} ({}/{}):({}/{})", r->url_, uln, ult, dlnow, dltotal);
106 return 0;
107 }
108
Request(Client * client,Method m,std::string url__)109 Request::Request(Client *client, Method m, std::string url__) : url_(std::move(url__)), global(client), method(m) {
110 this->easy = curl_easy_init();
111 if (!this->easy) {
112 Client::log->critical("curl_easy_init() failed, exiting!");
113 throw std::bad_alloc();
114 }
115
116 curl_easy_setopt(this->easy, CURLOPT_URL, this->url_.c_str());
117 curl_easy_setopt(this->easy, CURLOPT_WRITEFUNCTION, write_cb);
118 curl_easy_setopt(this->easy, CURLOPT_WRITEDATA, this);
119 curl_easy_setopt(this->easy, CURLOPT_HEADERFUNCTION, header_cb);
120 curl_easy_setopt(this->easy, CURLOPT_HEADERDATA, this);
121 // curl_easy_setopt(this->easy, CURLOPT_VERBOSE, 1L);
122 curl_easy_setopt(this->easy, CURLOPT_ERRORBUFFER, this->error);
123 curl_easy_setopt(this->easy, CURLOPT_PRIVATE, this);
124 curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 1L);
125 curl_easy_setopt(this->easy, CURLOPT_XFERINFOFUNCTION, prog_cb);
126 curl_easy_setopt(this->easy, CURLOPT_XFERINFODATA, this);
127 curl_easy_setopt(this->easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
128 // default to all supported encodings
129 curl_easy_setopt(this->easy, CURLOPT_ACCEPT_ENCODING, "");
130
131 switch (m) {
132 case Method::Delete:
133 curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 0L);
134 curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "DELETE");
135 break;
136 case Method::Get:
137 curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 1L);
138 break;
139 case Method::Head:
140 curl_easy_setopt(this->easy, CURLOPT_NOBODY, 1L);
141 break;
142 case Method::Options:
143 curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "OPTIONS");
144 break;
145 case Method::Patch:
146 curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "PATCH");
147 break;
148 case Method::Post:
149 curl_easy_setopt(this->easy, CURLOPT_POST, 1L);
150 request("");
151 break;
152 case Method::Put:
153 curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "PUT");
154 request("");
155 break;
156 }
157
158 verify_peer(this->global->does_verify_peer());
159 }
160
~Request()161 Request::~Request() {
162 curl_easy_cleanup(this->easy);
163 curl_slist_free_all(request_headers_);
164 }
165
max_redirects(long amount)166 Request &Request::max_redirects(long amount) {
167 curl_easy_setopt(this->easy, CURLOPT_FOLLOWLOCATION, 1L);
168 curl_easy_setopt(this->easy, CURLOPT_MAXREDIRS, amount);
169 return *this;
170 }
171
verify_peer(bool verify)172 Request &Request::verify_peer(bool verify) {
173 curl_easy_setopt(this->easy, CURLOPT_SSL_VERIFYPEER, verify ? 1L : 0L);
174 return *this;
175 }
176
request(std::string r,std::string contenttype)177 Request &Request::request(std::string r, std::string contenttype) {
178 this->request_ = std::move(r);
179 this->request_contenttype_ = std::move(contenttype);
180
181 curl_easy_setopt(this->easy, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(request_.size()));
182 curl_easy_setopt(this->easy, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(request_.size()));
183 curl_easy_setopt(this->easy, CURLOPT_READDATA, this);
184 curl_easy_setopt(this->easy, CURLOPT_READFUNCTION, read_cb);
185 curl_easy_setopt(this->easy, CURLOPT_POSTFIELDS, nullptr);
186 return *this;
187 }
188
request_headers(const Headers & h)189 Request &Request::request_headers(const Headers &h) {
190 if (request_headers_)
191 curl_slist_free_all(request_headers_);
192
193 for (const auto &[k, v] : h) {
194 request_headers_ = curl_slist_append(request_headers_, (k + ": " + v).c_str());
195 }
196
197 if (!request_contenttype_.empty())
198 request_headers_ = curl_slist_append(request_headers_, ("content-type: " + request_contenttype_).c_str());
199
200 curl_easy_setopt(this->easy, CURLOPT_HTTPHEADER, request_headers_);
201 return *this;
202 }
203
connection_timeout(long t)204 Request &Request::connection_timeout(long t) {
205 // only allow values that are > 0, if they are divided by 3
206 if (t <= 2)
207 return *this;
208
209 // enable keepalive
210 curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPALIVE, 1L);
211
212 // send keepalives every third of the timeout interval. This allows for
213 // retransmission of 2 keepalive while still giving the server a third of the
214 // time to reply
215 curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPIDLE, t / 3);
216 curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPINTVL, t / 3);
217
218 this->connection_timeout_ = t;
219 #ifdef TCP_USER_TIMEOUT
220 // The + is needed to convert this to a function pointer!
221 curl_easy_setopt(
222 this->easy, CURLOPT_SOCKOPTFUNCTION, +[](void *clientp, curl_socket_t curlfd, curlsocktype) -> int {
223 unsigned int val = static_cast<Request *>(clientp)->connection_timeout_ * 1000 /*ms*/;
224 setsockopt(curlfd, SOL_TCP, TCP_USER_TIMEOUT, (const char *)&val, sizeof(val));
225 return CURLE_OK;
226 });
227 #elif defined(TCP_MAXRT)
228 // The + is needed to convert this to a function pointer!
229 curl_easy_setopt(
230 this->easy, CURLOPT_SOCKOPTFUNCTION, +[](void *clientp, curl_socket_t sock, curlsocktype) -> int {
231 unsigned int maxrt = static_cast<Request *>(clientp)->connection_timeout_ /*s*/;
232 setsockopt(sock, IPPROTO_TCP, TCP_MAXRT, (const char *)&maxrt, sizeof(maxrt));
233 return CURLE_OK;
234 });
235 #endif
236 curl_easy_setopt(this->easy, CURLOPT_SOCKOPTDATA, this);
237
238 return *this;
239 }
240
on_complete(std::function<void (const Request &)> handler)241 Request &Request::on_complete(std::function<void(const Request &)> handler) {
242 on_complete_ = handler;
243 return *this;
244 }
245
on_upload_progress(std::function<void (size_t progress,size_t total)> handler)246 Request &Request::on_upload_progress(std::function<void(size_t progress, size_t total)> handler) {
247 on_upload_progress_ = handler;
248 curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 0L);
249 return *this;
250 }
on_download_progess(std::function<void (size_t progress,size_t total)> handler)251 Request &Request::on_download_progess(std::function<void(size_t progress, size_t total)> handler) {
252 on_download_progess_ = handler;
253 curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 0L);
254 return *this;
255 }
256
response_code() const257 int Request::response_code() const {
258 long http_code;
259 curl_easy_getinfo(const_cast<CURL *>(this->easy), CURLINFO_RESPONSE_CODE, &http_code);
260 return static_cast<int>(http_code);
261 }
262 } // namespace coeurl
263