/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 |