Coverage Report

Created: 2026-07-16 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/http.cpp
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