Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/futils.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "futils.hpp"
5
6
#include "config.h"
7
#include "enforce.hpp"
8
#include "image_int.hpp"
9
10
// + standard includes
11
#include <algorithm>
12
#include <array>
13
#include <cctype>
14
#include <cstdint>
15
#include <cstring>
16
#include <stdexcept>
17
#include <string>
18
19
#ifdef EXV_ENABLE_FILESYSTEM
20
#include <filesystem>
21
namespace fs = std::filesystem;
22
23
#ifdef _WIN32
24
// clang-format off
25
#include <windows.h>
26
#include <psapi.h>  // For access to GetModuleFileName
27
// clang-format on
28
#endif
29
30
#if __has_include(<unistd.h>)
31
#include <unistd.h>  // for getpid()
32
#endif
33
34
#if __has_include(<libproc.h>)
35
#include <libproc.h>
36
#endif
37
38
#if __has_include(<mach-o/dyld.h>)
39
#include <mach-o/dyld.h>  // for _NSGetExecutablePath()
40
#endif
41
42
#ifdef __FreeBSD__
43
// clang-format off
44
#include <sys/mount.h>
45
#include <sys/param.h>
46
#include <sys/queue.h>
47
#include <sys/socket.h>
48
#include <sys/sysctl.h>
49
#include <sys/types.h>
50
#include <sys/un.h>
51
#include <libprocstat.h>
52
// clang-format on
53
#endif
54
55
#ifndef _MAX_PATH
56
#define _MAX_PATH 1024
57
#endif
58
59
#endif
60
61
namespace {
62
constexpr std::array ENVARDEF{
63
    "/exiv2.php",
64
    "40",
65
};  /// @brief default URL for http exiv2 handler and time-out
66
constexpr std::array ENVARKEY{
67
    "EXIV2_HTTP_POST",
68
    "EXIV2_TIMEOUT",
69
};  /// @brief request keys for http exiv2 handler and time-out
70
71
/// @brief Convert an integer value to its hex character.
72
0
char to_hex(char code) {
73
0
  static const char hex[] = "0123456789abcdef";
74
0
  return hex[code & 15];
75
0
}
76
77
/// @brief Convert a hex character to its integer value.
78
0
char from_hex(char ch) {
79
0
  return 0xF & (isdigit(ch) ? ch - '0' : static_cast<char>(tolower(ch)) - 'a' + 10);
80
0
}
81
82
constexpr char base64_encode[] = {
83
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
84
    'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
85
    's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
86
};
87
}  // namespace
88
89
namespace Exiv2 {
90
// *****************************************************************************
91
// free functions
92
0
std::string getEnv(int env_var) {
93
  // this check is relying on undefined behavior and might not be effective
94
0
  if (env_var < envHTTPPOST || env_var > envTIMEOUT)
95
0
    throw std::out_of_range("Unexpected env variable");
96
#ifdef _WIN32
97
  char* buf = nullptr;
98
  size_t len;
99
  if (_dupenv_s(&buf, &len, ENVARKEY[env_var]) == 0 && buf) {
100
    auto ret = std::string(buf);
101
    free(buf);
102
    return ret;
103
  }
104
#else
105
0
  if (auto val = std::getenv(ENVARKEY[env_var]))
106
0
    return val;
107
0
#endif
108
0
  return ENVARDEF[env_var];
109
0
}
110
111
0
std::string urlencode(const std::string& str) {
112
0
  std::string encoded;
113
0
  encoded.reserve(str.size() * 3);
114
0
  for (uint8_t c : str) {
115
0
    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
116
0
      encoded += c;
117
0
    else if (c == ' ')
118
0
      encoded += '+';
119
0
    else {
120
0
      encoded += '%';
121
0
      encoded += to_hex(c >> 4);
122
0
      encoded += to_hex(c & 15);
123
0
    }
124
0
  }
125
0
  encoded.shrink_to_fit();
126
0
  return encoded;
127
0
}
128
129
0
void urldecode(std::string& str) {
130
0
  size_t idxIn{0};
131
0
  size_t idxOut{0};
132
0
  size_t sizeStr = str.size();
133
0
  while (idxIn < sizeStr) {
134
0
    if (str[idxIn] == '%') {
135
0
      if (str[idxIn + 1] && str[idxIn + 2]) {
136
0
        str[idxOut++] = from_hex(str[idxIn + 1]) << 4 | from_hex(str[idxIn + 2]);
137
0
        idxIn += 2;
138
0
      }
139
0
    } else if (str[idxIn] == '+') {
140
0
      str[idxOut++] = ' ';
141
0
    } else {
142
0
      str[idxOut++] = str[idxIn];
143
0
    }
144
0
    idxIn++;
145
0
  }
146
0
  str.erase(idxOut);
147
0
}
148
149
// https://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c
150
0
int base64encode(const void* data_buf, size_t dataLength, char* result, size_t resultSize) {
151
0
  auto encoding_table = base64_encode;
152
153
0
  size_t output_length = 4 * ((dataLength + 2) / 3);
154
0
  int rc = result && data_buf && output_length < resultSize ? 1 : 0;
155
0
  if (rc) {
156
0
    const auto data = static_cast<const unsigned char*>(data_buf);
157
0
    for (size_t i = 0, j = 0; i < dataLength;) {
158
0
      uint32_t octet_a = data[i++];
159
0
      uint32_t octet_b = i < dataLength ? data[i++] : 0;
160
0
      uint32_t octet_c = i < dataLength ? data[i++] : 0;
161
162
0
      uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
163
164
0
      result[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
165
0
      result[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
166
0
      result[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
167
0
      result[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
168
0
    }
169
170
0
    const size_t mod_table[] = {0, 2, 1};
171
0
    for (size_t i = 0; i < mod_table[dataLength % 3]; i++)
172
0
      result[output_length - 1 - i] = '=';
173
0
    result[output_length] = 0;
174
0
  }
175
0
  return rc;
176
0
}  // base64encode
177
178
0
size_t base64decode(const char* in, char* out, size_t out_size) {
179
0
  size_t result = 0;
180
0
  size_t input_length = in ? ::strlen(in) : 0;
181
0
  if (!in || input_length % 4 != 0 || input_length == 0)
182
0
    return result;
183
184
0
  auto encoding_table = reinterpret_cast<const unsigned char*>(base64_encode);
185
0
  unsigned char decoding_table[256];
186
0
  for (unsigned char i = 0; i < 64; i++)
187
0
    decoding_table[encoding_table[i]] = i;
188
189
0
  size_t output_length = input_length / 4 * 3;
190
0
  const auto buff = reinterpret_cast<const unsigned char*>(in);
191
192
0
  if (buff[input_length - 1] == '=')
193
0
    output_length--;
194
0
  if (buff[input_length - 2] == '=')
195
0
    output_length--;
196
197
0
  if (output_length + 1 < out_size) {
198
0
    for (size_t i = 0, j = 0; i < input_length;) {
199
0
      uint32_t sextet_a = buff[i] == '=' ? 0 & i++ : decoding_table[buff[i++]];
200
0
      uint32_t sextet_b = buff[i] == '=' ? 0 & i++ : decoding_table[buff[i++]];
201
0
      uint32_t sextet_c = buff[i] == '=' ? 0 & i++ : decoding_table[buff[i++]];
202
0
      uint32_t sextet_d = buff[i] == '=' ? 0 & i++ : decoding_table[buff[i++]];
203
204
0
      uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6);
205
206
0
      if (j < output_length)
207
0
        out[j++] = (triple >> 2 * 8) & 0xFF;
208
0
      if (j < output_length)
209
0
        out[j++] = (triple >> 1 * 8) & 0xFF;
210
0
      if (j < output_length)
211
0
        out[j++] = (triple >> 0 * 8) & 0xFF;
212
0
    }
213
0
    out[output_length] = 0;
214
0
    result = output_length;
215
0
  }
216
217
0
  return result;
218
0
}
219
220
38.8k
Protocol fileProtocol(const std::string& path) {
221
38.8k
  Protocol result = pFile;
222
38.8k
  constexpr struct {
223
38.8k
    std::string_view name;
224
38.8k
    Protocol prot;
225
38.8k
    bool isUrl;  // path.size() > name.size()
226
38.8k
  } prots[] = {
227
38.8k
      {"http://", pHttp, true},    {"https://", pHttps, true},  {"ftp://", pFtp, true}, {"sftp://", pSftp, true},
228
38.8k
      {"file://", pFileUri, true}, {"data://", pDataUri, true}, {"-", pStdin, false},
229
38.8k
  };
230
271k
  for (const auto& prot : prots) {
231
271k
    if (result != pFile)
232
0
      break;
233
234
271k
    if (path.starts_with(prot.name))
235
      // URL's require data.  Stdin == "-" and no further data
236
0
      if (prot.isUrl ? path.size() > prot.name.size() : path.size() == prot.name.size())
237
0
        result = prot.prot;
238
271k
  }
239
240
38.8k
  return result;
241
38.8k
}  // fileProtocol
242
243
0
bool fileExists(const std::string& path) {
244
0
  if (fileProtocol(path) != pFile) {
245
0
    return true;
246
0
  }
247
0
#ifdef EXV_ENABLE_FILESYSTEM
248
0
  return fs::exists(path);
249
#else
250
  return false;
251
#endif
252
0
}
253
254
1.39k
std::string strError() {
255
1.39k
  int error = errno;
256
1.39k
  std::string os;
257
1.39k
#ifdef EXV_HAVE_STRERROR_R
258
1.39k
  const size_t n = 1024;
259
1.39k
#ifdef EXV_STRERROR_R_CHAR_P
260
1.39k
  char buf2[n] = {};
261
1.39k
  auto buf = strerror_r(error, buf2, n);
262
#else
263
  char buf[n] = {};
264
  const int ret = strerror_r(error, buf, n);
265
  Internal::enforce(ret != ERANGE, Exiv2::ErrorCode::kerCallFailed);
266
#endif
267
1.39k
  os = buf;
268
  // Issue# 908.
269
  // report strerror() if strerror_r() returns empty
270
1.39k
  if (os.empty()) {
271
0
    os = std::strerror(error);
272
0
  }
273
#elif defined(_WIN32)
274
  const size_t n = 1024;
275
  char buf[n] = {};
276
  const auto ret = strerror_s(buf, n, error);
277
  Internal::enforce(ret != ERANGE, Exiv2::ErrorCode::kerCallFailed);
278
  os = buf;
279
#else
280
  os = std::strerror(error);
281
#endif
282
1.39k
  return stringFormat("{} (errno = {})", os, error);
283
1.39k
}  // strError
284
285
0
void Uri::Decode(Uri& uri) {
286
0
  urldecode(uri.QueryString);
287
0
  urldecode(uri.Path);
288
0
  urldecode(uri.Host);
289
0
  urldecode(uri.Username);
290
0
  urldecode(uri.Password);
291
0
}
292
293
0
Uri Uri::Parse(const std::string& uri) {
294
0
  Uri result;
295
296
0
  if (uri.empty())
297
0
    return result;
298
299
0
  const auto uriEnd = uri.end();
300
301
  // protocol
302
0
  const auto protocolStart = uri.begin();
303
0
  auto protocolEnd = std::find(protocolStart, uriEnd, ':');  //"://");
304
305
0
  assert(protocolStart <= protocolEnd);
306
307
0
  if (protocolEnd != uriEnd) {
308
0
    auto prot = std::string(protocolEnd, uriEnd);
309
0
    if (prot.starts_with("://")) {
310
0
      result.Protocol = std::string(protocolStart, protocolEnd);
311
0
      protocolEnd += 3;  //      ://
312
0
    } else
313
0
      protocolEnd = protocolStart;  // no protocol
314
0
  } else
315
0
    protocolEnd = protocolStart;  // no protocol
316
317
0
  assert(protocolStart <= protocolEnd);
318
319
  // username & password
320
0
  const auto authStart = protocolEnd;
321
322
0
  auto authEnd = std::find(authStart, uriEnd, '@');
323
0
  assert(authStart <= authEnd);
324
0
  if (authEnd != uriEnd) {
325
0
    const auto userStart = authStart;
326
0
    if (auto userEnd = std::find(authStart, authEnd, ':'); userEnd != authEnd) {
327
0
      assert(authStart <= userEnd);
328
0
      assert(userEnd < authEnd);
329
0
      result.Username = std::string(userStart, userEnd);
330
0
      ++userEnd;
331
0
      assert(userEnd <= authEnd);
332
0
      result.Password = std::string(userEnd, authEnd);
333
0
    } else {
334
0
      result.Username = std::string(authStart, authEnd);
335
0
    }
336
0
    ++authEnd;
337
0
  } else {
338
0
    authEnd = protocolEnd;
339
0
  }
340
341
0
  assert(authStart <= authEnd);
342
343
  // host
344
0
  const auto hostStart = authEnd;
345
0
  const auto pathStart = std::find(hostStart, uriEnd, '/');   // get pathStart
346
0
  const auto queryStart = std::find(hostStart, uriEnd, '?');  // get query start
347
348
0
  assert(hostStart <= pathStart);
349
0
  assert(hostStart <= queryStart);
350
351
0
  const auto portEnd = (pathStart < uriEnd) ? pathStart : queryStart;
352
0
  auto hostEnd = std::find(hostStart, portEnd, ':');  // check for port
353
354
0
  assert(hostStart <= hostEnd);
355
0
  assert(hostEnd <= portEnd);
356
357
0
  if (hostStart < hostEnd) {
358
0
    result.Host = std::string(hostStart, hostEnd);
359
0
  }
360
361
  // port
362
0
  if ((hostEnd < uriEnd) && (*hostEnd == ':'))  // we have a port
363
0
  {
364
0
    ++hostEnd;
365
0
    if (hostEnd < portEnd) {
366
0
      result.Port = std::string(hostEnd, portEnd);
367
0
    }
368
0
  }
369
0
  if (result.Port.empty() && result.Protocol == "http")
370
0
    result.Port = "80";
371
372
  // path
373
0
  if (pathStart < queryStart) {
374
0
    result.Path = std::string(pathStart, queryStart);
375
0
  }
376
377
  // query
378
0
  if (queryStart < uriEnd)
379
0
    result.QueryString = std::string(queryStart, uriEnd);
380
381
0
  return result;
382
0
}
383
384
0
std::string getProcessPath() {
385
0
#ifdef EXV_ENABLE_FILESYSTEM
386
#ifdef __FreeBSD__
387
  std::string ret("unknown");
388
  unsigned int n;
389
  char buffer[PATH_MAX] = {};
390
  struct procstat* procstat = procstat_open_sysctl();
391
  struct kinfo_proc* procs = procstat ? procstat_getprocs(procstat, KERN_PROC_PID, getpid(), &n) : nullptr;
392
  if (procs) {
393
    procstat_getpathname(procstat, procs, buffer, PATH_MAX);
394
    ret = std::string(buffer);
395
  }
396
  // release resources
397
  if (procs)
398
    procstat_freeprocs(procstat, procs);
399
  if (procstat)
400
    procstat_close(procstat);
401
402
  const size_t idxLastSeparator = ret.find_last_of('/');
403
  return ret.substr(0, idxLastSeparator);
404
#else
405
0
  try {
406
#ifdef _WIN32
407
    TCHAR pathbuf[MAX_PATH];
408
    GetModuleFileName(nullptr, pathbuf, MAX_PATH);
409
    auto path = fs::path(pathbuf);
410
#elif defined(__APPLE__)
411
    char pathbuf[2048];
412
    uint32_t size = sizeof(pathbuf);
413
    const int get_exec_path_failure = _NSGetExecutablePath(pathbuf, &size);
414
    if (get_exec_path_failure)
415
      return "unknown";  // pathbuf not big enough
416
    auto path = fs::path(pathbuf);
417
#elif defined(__sun__)
418
    auto path = fs::read_symlink(stringFormat("/proc/{}/path/a.out", getpid()));
419
#elif defined(__unix__)
420
    auto path = fs::read_symlink("/proc/self/exe");
421
0
#endif
422
0
    return path.parent_path().string();
423
0
  } catch (const fs::filesystem_error&) {
424
0
    return "unknown";
425
0
  }
426
0
#endif
427
#else
428
  return "unknown";
429
#endif
430
0
}
431
}  // namespace Exiv2