Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | #include "http.hpp" |
4 | | #include "config.h" |
5 | | #include "futils.hpp" |
6 | | |
7 | | #include <array> |
8 | | #include <cerrno> |
9 | | #include <chrono> |
10 | | #include <cstring> |
11 | | #include <thread> |
12 | | |
13 | | //////////////////////////////////////// |
14 | | // platform specific code |
15 | | |
16 | | #ifndef _WIN32 |
17 | | //////////////////////////////////////// |
18 | | // Unix or Mac |
19 | | |
20 | 0 | #define closesocket close |
21 | | |
22 | | #include <arpa/inet.h> |
23 | | #include <fcntl.h> |
24 | | #include <netdb.h> |
25 | | #include <netinet/in.h> |
26 | | #include <sys/socket.h> |
27 | | #include <unistd.h> |
28 | | |
29 | 0 | #define INVALID_SOCKET (-1) |
30 | 0 | #define SOCKET_ERROR (-1) |
31 | 0 | #define WSAEWOULDBLOCK EINPROGRESS |
32 | 0 | #define WSAENOTCONN EAGAIN |
33 | | |
34 | 0 | static int WSAGetLastError() { |
35 | 0 | return errno; |
36 | 0 | } |
37 | | #else |
38 | | #include <winsock2.h> |
39 | | #include <ws2tcpip.h> |
40 | | #endif |
41 | | |
42 | | //////////////////////////////////////// |
43 | | // code |
44 | | static constexpr auto httpTemplate = |
45 | | "%s %s HTTP/%s\r\n" // $verb $page $version |
46 | | "User-Agent: exiv2http/1.0.0\r\n" |
47 | | "Accept: */*\r\n" |
48 | | "Host: %s\r\n" // $servername |
49 | | "%s" // $header |
50 | | "\r\n"; |
51 | | |
52 | | #define white(c) (((c) == ' ') || ((c) == '\t')) |
53 | | |
54 | 0 | #define FINISH (-999) |
55 | 0 | #define OK(s) (200 <= (s) && (s) < 300) |
56 | | |
57 | | static constexpr std::array blankLines{ |
58 | | "\r\n\r\n", // this is the standard |
59 | | "\n\n", // this is commonly sent by CGI scripts |
60 | | }; |
61 | | |
62 | | static constexpr auto snooze = std::chrono::milliseconds::zero(); |
63 | | static auto sleep_ = std::chrono::milliseconds(1000); |
64 | | |
65 | 0 | static int forgive(int n, int& err) { |
66 | 0 | err = WSAGetLastError(); |
67 | 0 | if (!n && !err) |
68 | 0 | return FINISH; |
69 | 0 | #ifndef _WIN32 |
70 | 0 | if (n == 0) |
71 | 0 | return FINISH; // server hungup |
72 | 0 | #endif |
73 | 0 | if (n == SOCKET_ERROR && (err == WSAEWOULDBLOCK || err == WSAENOTCONN)) |
74 | 0 | return 0; |
75 | 0 | return n; |
76 | 0 | } |
77 | | |
78 | 0 | static int error(std::string& errors, const char* msg, const char* x = nullptr, const char* y = nullptr, int z = 0) { |
79 | 0 | static const size_t buffer_size = 512; |
80 | 0 | char buffer[buffer_size] = {}; |
81 | 0 | snprintf(buffer, buffer_size, msg, x, y, z); |
82 | 0 | if (errno) { |
83 | 0 | perror(buffer); |
84 | 0 | } else { |
85 | 0 | fprintf(stderr, "%s\n", buffer); |
86 | 0 | } |
87 | 0 | errors += std::string(msg) + '\n'; |
88 | 0 | return -1; |
89 | 0 | } |
90 | | |
91 | 0 | static void flushBuffer(const char* buffer, size_t start, int& end, std::string& file) { |
92 | 0 | file += std::string(buffer + start, end - start); |
93 | 0 | end = 0; |
94 | 0 | } |
95 | | |
96 | 0 | static Exiv2::Dictionary stringToDict(const std::string& s) { |
97 | 0 | Exiv2::Dictionary result; |
98 | 0 | std::string token; |
99 | |
|
100 | 0 | size_t i = 0; |
101 | 0 | while (i < s.length()) { |
102 | 0 | if (s[i] != ',') { |
103 | 0 | if (s[i] != ' ') |
104 | 0 | token += s[i]; |
105 | 0 | } else { |
106 | 0 | result[token] = token; |
107 | 0 | token.clear(); |
108 | 0 | } |
109 | 0 | i++; |
110 | 0 | } |
111 | 0 | result[token] = token; |
112 | 0 | return result; |
113 | 0 | } |
114 | | |
115 | 0 | int Exiv2::http(Exiv2::Dictionary& request, Exiv2::Dictionary& response, std::string& errors) { |
116 | 0 | request.try_emplace("verb", "GET"); |
117 | 0 | request.try_emplace("header"); |
118 | 0 | request.try_emplace("version", "1.0"); |
119 | 0 | request.try_emplace("port"); |
120 | |
|
121 | 0 | std::string file; |
122 | 0 | errors = ""; |
123 | 0 | int result = 0; |
124 | | |
125 | | //////////////////////////////////// |
126 | | // Windows specific code |
127 | | #ifdef _WIN32 |
128 | | WSADATA wsaData; |
129 | | if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) |
130 | | return error(errors, "could not start WinSock"); |
131 | | #endif |
132 | |
|
133 | 0 | const char* servername = request["server"].c_str(); |
134 | 0 | const char* page = request["page"].c_str(); |
135 | 0 | const char* verb = request["verb"].c_str(); |
136 | 0 | const char* header = request["header"].c_str(); |
137 | 0 | const char* version = request["version"].c_str(); |
138 | 0 | const char* port = request["port"].c_str(); |
139 | |
|
140 | 0 | const char* servername_p = servername; |
141 | 0 | const char* port_p = port; |
142 | 0 | std::string url = std::string("http://") + request["server"] + request["page"]; |
143 | | |
144 | | // parse and change server if using a proxy |
145 | 0 | const char* PROXI = "HTTP_PROXY"; |
146 | 0 | const char* proxi = "http_proxy"; |
147 | 0 | const char* PROXY = getenv(PROXI); |
148 | 0 | const char* proxy = getenv(proxi); |
149 | 0 | bool bProx = PROXY || proxy; |
150 | 0 | const char* prox = bProx ? (proxy ? proxy : PROXY) : ""; |
151 | 0 | Exiv2::Uri Proxy = Exiv2::Uri::Parse(prox); |
152 | | |
153 | | // find the dictionary of no_proxy servers |
154 | 0 | const char* NO_PROXI = "NO_PROXY"; |
155 | 0 | const char* no_proxi = "no_proxy"; |
156 | 0 | const char* NO_PROXY = getenv(NO_PROXI); |
157 | 0 | const char* no_proxy = getenv(no_proxi); |
158 | 0 | bool bNoProxy = NO_PROXY || no_proxy; |
159 | 0 | auto no_prox = std::string(bNoProxy ? (no_proxy ? no_proxy : NO_PROXY) : ""); |
160 | 0 | Exiv2::Dictionary noProxy = stringToDict(no_prox + ",localhost,127.0.0.1"); |
161 | | |
162 | | // if the server is on the no_proxy list ... ignore the proxy! |
163 | 0 | if (noProxy.contains(servername)) |
164 | 0 | bProx = false; |
165 | |
|
166 | 0 | if (bProx) { |
167 | 0 | servername_p = Proxy.Host.c_str(); |
168 | 0 | port_p = Proxy.Port.c_str(); |
169 | 0 | page = url.c_str(); |
170 | 0 | } |
171 | 0 | if (!port[0]) |
172 | 0 | port = "80"; |
173 | 0 | if (!port_p[0]) |
174 | 0 | port_p = "80"; |
175 | | |
176 | | //////////////////////////////////// |
177 | | // open the socket |
178 | 0 | auto sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
179 | 0 | if (sockfd == INVALID_SOCKET) |
180 | 0 | return error(errors, "unable to create socket\n", nullptr, nullptr, 0); |
181 | | |
182 | | // fill in the address |
183 | 0 | sockaddr_in serv_addr = {}; |
184 | 0 | int serv_len = sizeof(serv_addr); |
185 | | |
186 | | // convert unknown servername into IP address |
187 | | // http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/rzab6/rzab6uafinet.htm |
188 | 0 | if (inet_pton(AF_INET, servername_p, &serv_addr.sin_addr) != 0) { |
189 | 0 | struct addrinfo hints = {}; |
190 | 0 | hints.ai_family = AF_INET; |
191 | 0 | hints.ai_socktype = SOCK_STREAM; |
192 | 0 | struct addrinfo* r; |
193 | |
|
194 | 0 | int res = getaddrinfo(servername_p, port_p, &hints, &r); |
195 | 0 | if (res != 0) { |
196 | 0 | closesocket(sockfd); |
197 | 0 | return error(errors, "no such host: %s", gai_strerror(res)); |
198 | 0 | } |
199 | | |
200 | 0 | std::memcpy(&serv_addr, r->ai_addr, serv_len); |
201 | |
|
202 | 0 | freeaddrinfo(r); |
203 | 0 | } |
204 | | |
205 | 0 | [](auto sockfd) { |
206 | | #if defined(_WIN32) |
207 | | ULONG ioctl_opt = 1; |
208 | | return ioctlsocket(sockfd, FIONBIO, &ioctl_opt); |
209 | | #else |
210 | 0 | int result = fcntl(sockfd, F_SETFL, O_NONBLOCK); |
211 | 0 | return result >= 0 ? result : SOCKET_ERROR; |
212 | 0 | #endif |
213 | 0 | }(sockfd); |
214 | | |
215 | | //////////////////////////////////// |
216 | | // and connect |
217 | 0 | auto server = connect(sockfd, reinterpret_cast<const sockaddr*>(&serv_addr), serv_len); |
218 | 0 | if (server == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { |
219 | 0 | closesocket(sockfd); |
220 | 0 | return error(errors, "error - unable to connect to server = %s port = %s wsa_error = %d", servername_p, |
221 | 0 | std::to_string(serv_addr.sin_port).c_str(), WSAGetLastError()); |
222 | 0 | } |
223 | | |
224 | 0 | char buffer[(32 * 1024) + 1]; |
225 | 0 | size_t buff_l = sizeof buffer - 1; |
226 | | |
227 | | //////////////////////////////////// |
228 | | // format the request |
229 | 0 | int n = snprintf(buffer, buff_l, httpTemplate, verb, page, version, servername, header); |
230 | 0 | buffer[n] = 0; |
231 | 0 | response["requestheaders"] = std::string(buffer, n); |
232 | | |
233 | | //////////////////////////////////// |
234 | | // send the header (we'll have to wait for the connection by the non-blocking socket) |
235 | 0 | while (sleep_ >= std::chrono::milliseconds::zero()) { |
236 | 0 | auto sent = send(sockfd, buffer, n, 0); |
237 | 0 | if (sent != SOCKET_ERROR) |
238 | 0 | break; |
239 | | // auto err = WSAGetLastError(); |
240 | | // if (err != WSAENOTCONN && err != WSAEWOULDBLOCK) |
241 | | // break; |
242 | 0 | std::this_thread::sleep_for(snooze); |
243 | 0 | sleep_ -= snooze; |
244 | 0 | } |
245 | |
|
246 | 0 | if (sleep_ < std::chrono::milliseconds::zero()) { |
247 | 0 | closesocket(sockfd); |
248 | 0 | return error(errors, "error - timeout connecting to server = %s port = %s wsa_error = %d", servername, port, |
249 | 0 | WSAGetLastError()); |
250 | 0 | } |
251 | | |
252 | 0 | int end = 0; // write position in buffer |
253 | 0 | bool bSearching = true; // looking for headers in the response |
254 | 0 | int status = 200; // assume happiness |
255 | | |
256 | | //////////////////////////////////// |
257 | | // read and process the response |
258 | 0 | int err = 0; |
259 | 0 | n = forgive(recv(sockfd, buffer, static_cast<int>(buff_l), 0), err); |
260 | 0 | while (n >= 0 && OK(status)) { |
261 | 0 | if (n) { |
262 | 0 | end += n; |
263 | 0 | buffer[end] = 0; |
264 | |
|
265 | 0 | size_t body = 0; // start of body |
266 | 0 | if (bSearching) { |
267 | | // search for the body |
268 | 0 | for (auto&& line : blankLines) { |
269 | 0 | if (!bSearching) |
270 | 0 | break; |
271 | 0 | const char* blankLinePos = strstr(buffer, line); |
272 | 0 | if (blankLinePos) { |
273 | 0 | bSearching = false; |
274 | 0 | body = blankLinePos - buffer + strlen(line); |
275 | 0 | const char* firstSpace = strchr(buffer, ' '); |
276 | 0 | if (firstSpace) { |
277 | 0 | status = atoi(firstSpace); |
278 | 0 | } |
279 | 0 | } |
280 | 0 | } |
281 | | |
282 | | // parse response headers |
283 | 0 | char* h = buffer; |
284 | 0 | char C = ':'; |
285 | 0 | char N = '\n'; |
286 | 0 | int i = 0; // initial byte in buffer |
287 | 0 | while (buffer[i] == N) |
288 | 0 | i++; |
289 | 0 | h = strchr(h + i, N); |
290 | 0 | if (!h) { |
291 | 0 | status = 0; |
292 | 0 | break; |
293 | 0 | } |
294 | 0 | h++; |
295 | 0 | response[""] = std::string(buffer + i).substr(0, h - buffer - 2); |
296 | 0 | const char* firstSpace = strchr(buffer, ' '); |
297 | 0 | if (!firstSpace) { |
298 | 0 | status = 0; |
299 | 0 | break; |
300 | 0 | } |
301 | 0 | result = atoi(firstSpace); |
302 | 0 | auto c = strchr(h, C); |
303 | 0 | char* first_newline = strchr(h, N); |
304 | 0 | while (c && first_newline && c < first_newline && h < buffer + body) { |
305 | 0 | std::string key(h); |
306 | 0 | std::string value(c + 1); |
307 | 0 | key.resize(c - h); |
308 | 0 | value.resize(first_newline - c - 1); |
309 | 0 | response[key] = std::move(value); |
310 | 0 | h = first_newline + 1; |
311 | 0 | c = strchr(h, C); |
312 | 0 | first_newline = strchr(h, N); |
313 | 0 | } |
314 | 0 | } |
315 | | |
316 | | // if the buffer's full and we're still searching - give up! |
317 | | // this handles the possibility that there are no headers |
318 | 0 | if (bSearching && buff_l - end < 10) { |
319 | 0 | bSearching = false; |
320 | 0 | body = 0; |
321 | 0 | } |
322 | 0 | if (!bSearching && OK(status)) { |
323 | 0 | flushBuffer(buffer, body, end, file); |
324 | 0 | } |
325 | 0 | } |
326 | 0 | n = forgive(recv(sockfd, buffer + end, static_cast<int>(buff_l - end), 0), err); |
327 | 0 | if (!n) { |
328 | 0 | std::this_thread::sleep_for(snooze); |
329 | 0 | sleep_ -= snooze; |
330 | 0 | if (sleep_ < std::chrono::milliseconds::zero()) |
331 | 0 | n = FINISH; |
332 | 0 | } |
333 | 0 | } |
334 | |
|
335 | 0 | if (n != FINISH || !OK(status)) { |
336 | 0 | snprintf(buffer, sizeof buffer, "wsa_error = %d,n = %d,sleep_ = %d status = %d", WSAGetLastError(), n, |
337 | 0 | static_cast<int>(sleep_.count()), status); |
338 | 0 | error(errors, buffer, nullptr, nullptr, 0); |
339 | 0 | } else if (bSearching && OK(status)) { |
340 | 0 | if (end) { |
341 | | // we finished OK without finding headers, flush the buffer |
342 | 0 | flushBuffer(buffer, 0, end, file); |
343 | 0 | } else { |
344 | 0 | closesocket(sockfd); |
345 | 0 | return error(errors, "error - no response from server = %s port = %s wsa_error = %d", servername, port, |
346 | 0 | WSAGetLastError()); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | //////////////////////////////////// |
351 | | // close sockets |
352 | 0 | closesocket(server); |
353 | 0 | closesocket(sockfd); |
354 | 0 | response["body"] = std::move(file); |
355 | 0 | return result; |
356 | 0 | } |
357 | | |
358 | | // That's all Folks |