Coverage Report

Created: 2026-08-25 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/basicio.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "basicio.hpp"
5
#include "config.h"
6
#include "datasets.hpp"
7
#include "enforce.hpp"
8
#include "error.hpp"
9
#include "futils.hpp"
10
#include "http.hpp"
11
#include "image_int.hpp"
12
#include "types.hpp"
13
14
#include <algorithm>
15
#include <cstdio>   // for remove, rename
16
#include <cstdlib>  // for alloc, realloc, free
17
#include <cstring>  // std::memcpy
18
#include <ctime>    // timestamp for the name of temporary file
19
#include <fstream>  // write the temporary file
20
#include <iostream>
21
#include <stdexcept>  // std::logic_error from std::stoll
22
23
#if __has_include(<sys/mman.h>)
24
#include <sys/mman.h>  // for mmap and munmap
25
#endif
26
#if __has_include(<process.h>)
27
#include <process.h>
28
#endif
29
#if __has_include(<unistd.h>)
30
#include <unistd.h>
31
#endif
32
33
#ifdef EXV_USE_CURL
34
#include <curl/curl.h>
35
#endif
36
37
#ifdef EXV_ENABLE_FILESYSTEM
38
#include <filesystem>
39
#ifdef _WIN32
40
#include <fcntl.h>  // _O_BINARY in FileIo::FileIo
41
#include <io.h>
42
#include <windows.h>
43
#endif
44
namespace fs = std::filesystem;
45
#endif
46
47
#ifndef _WIN32
48
2.71k
#define _fileno fileno
49
0
#define _isatty isatty
50
#endif
51
52
// When RemoteIo is used to access a file, the remote server can claim
53
// that the file is ludicrously large, which will cause a large allocation
54
// on the client side (Exiv2). To avoid this, we will throw an exception if
55
// the file size is larger than this limit.
56
static constexpr int MAX_REMOTE_FILE_SIZE = 0x8000000;
57
58
namespace Exiv2 {
59
60
35.5k
BasicIo::~BasicIo() = default;
61
62
322k
void BasicIo::readOrThrow(byte* buf, size_t rcount, ErrorCode err) {
63
322k
  const size_t nread = read(buf, rcount);
64
322k
  Internal::enforce(nread == rcount, err);
65
322k
  Internal::enforce(!error(), err);
66
322k
}
67
68
43.3k
void BasicIo::seekOrThrow(int64_t offset, Position pos, ErrorCode err) {
69
43.3k
  const int r = seek(offset, pos);
70
43.3k
  Internal::enforce(r == 0, err);
71
43.3k
}
72
73
#ifdef EXV_ENABLE_FILESYSTEM
74
//! Internal Pimpl structure of class FileIo.
75
class FileIo::Impl {
76
 public:
77
  //! Constructor
78
  explicit Impl(std::string path);
79
#ifdef _WIN32
80
  explicit Impl(std::wstring path);
81
#endif
82
35.5k
  ~Impl() = default;
83
  // Enumerations
84
  //! Mode of operation
85
  enum OpMode { opRead, opWrite, opSeek };
86
  // DATA
87
  std::string path_;  //!< (Standard) path
88
#ifdef _WIN32
89
  std::wstring wpath_;  //!< UCS2 path
90
#endif
91
  std::string openMode_;   //!< File open mode
92
  FILE* fp_{};             //!< File stream pointer
93
  OpMode opMode_{opSeek};  //!< File open mode
94
95
#ifdef _WIN32
96
  HANDLE hFile_{};  //!< Duplicated fd
97
  HANDLE hMap_{};   //!< Handle from CreateFileMapping
98
#endif
99
  byte* pMappedArea_{};    //!< Pointer to the memory-mapped area
100
  size_t mappedLength_{};  //!< Size of the memory-mapped area
101
  bool isWriteable_{};     //!< Can the mapped area be written to?
102
  // TYPES
103
  //! Simple struct stat wrapper for internal use
104
  struct StructStat {
105
    fs::perms st_mode{};       //!< Permissions
106
    std::uintmax_t st_size{};  //!< Size
107
  };
108
  // #endif
109
  // METHODS
110
  /*!
111
    @brief Switch to a new access mode, reopening the file if needed.
112
        Optimized to only reopen the file when it is really necessary.
113
    @param opMode The mode to switch to.
114
    @return 0 if successful
115
   */
116
  int switchMode(OpMode opMode);
117
  //! stat wrapper for internal use
118
  int stat(StructStat& buf) const;
119
  // NOT IMPLEMENTED
120
  Impl(const Impl&) = delete;             //!< Copy constructor
121
  Impl& operator=(const Impl&) = delete;  //!< Assignment
122
};
123
124
35.5k
FileIo::Impl::Impl(std::string path) : path_(std::move(path)) {
125
#ifdef _WIN32
126
  wchar_t t[512];
127
  const auto nw = MultiByteToWideChar(CP_UTF8, 0, path_.data(), static_cast<int>(path_.size()), t, 512);
128
  wpath_.assign(t, nw);
129
#endif
130
35.5k
}
131
#ifdef _WIN32
132
FileIo::Impl::Impl(std::wstring path) : wpath_(std::move(path)) {
133
  char t[1024];
134
  const auto nc =
135
      WideCharToMultiByte(CP_UTF8, 0, wpath_.data(), static_cast<int>(wpath_.size()), t, 1024, nullptr, nullptr);
136
  path_.assign(t, nc);
137
}
138
#endif
139
140
8.91M
int FileIo::Impl::switchMode(OpMode opMode) {
141
8.91M
  if (opMode_ == opMode)
142
8.19M
    return 0;
143
717k
  OpMode oldOpMode = opMode_;
144
717k
  opMode_ = opMode;
145
146
717k
  bool reopen = true;
147
717k
  switch (opMode) {
148
374k
    case opRead:
149
      // Flush if current mode allows reading, else reopen (in mode "r+b"
150
      // as in this case we know that we can write to the file)
151
374k
      if (openMode_.front() == 'r' || openMode_.at(1) == '+')
152
374k
        reopen = false;
153
374k
      break;
154
0
    case opWrite:
155
      // Flush if current mode allows writing, else reopen
156
0
      if (openMode_.front() != 'r' || openMode_.at(1) == '+')
157
0
        reopen = false;
158
0
      break;
159
342k
    case opSeek:
160
342k
      reopen = false;
161
342k
      break;
162
717k
  }
163
164
717k
  if (!reopen) {
165
    // Don't do anything when switching _from_ opSeek mode; we
166
    // flush when switching _to_ opSeek.
167
717k
    if (oldOpMode == opSeek)
168
374k
      return 0;
169
170
    // Flush. On msvcrt fflush does not do the job
171
342k
    std::fseek(fp_, 0, SEEK_CUR);
172
342k
    return 0;
173
717k
  }
174
175
  // Reopen the file
176
#ifdef _WIN32
177
  auto offset = _ftelli64(fp_);
178
#else
179
0
  auto offset = ftello(fp_);
180
0
#endif
181
0
  if (offset == -1)
182
0
    return -1;
183
  // 'Manual' open("r+b") to avoid munmap()
184
0
  std::fclose(fp_);
185
0
  openMode_ = "r+b";
186
0
  opMode_ = opSeek;
187
#ifdef _WIN32
188
  if (_wfopen_s(&fp_, wpath_.c_str(), L"r+b"))
189
    return 1;
190
  return _fseeki64(fp_, offset, SEEK_SET);
191
#else
192
0
  fp_ = std::fopen(path_.c_str(), openMode_.c_str());
193
0
  if (!fp_)
194
0
    return 1;
195
0
  return fseeko(fp_, offset, SEEK_SET);
196
0
#endif
197
0
}  // FileIo::Impl::switchMode
198
199
274k
int FileIo::Impl::stat(StructStat& buf) const {
200
#ifdef _WIN32
201
  const auto& file = wpath_;
202
#else
203
274k
  const auto& file = path_;
204
274k
#endif
205
274k
  try {
206
274k
    buf.st_size = fs::file_size(file);
207
274k
    buf.st_mode = fs::status(file).permissions();
208
274k
    return 0;
209
274k
  } catch (const fs::filesystem_error&) {
210
0
    return -1;
211
0
  }
212
274k
}  // FileIo::Impl::stat
213
214
35.5k
FileIo::FileIo(const std::string& path) : p_(std::make_unique<Impl>(path)) {
215
35.5k
}
216
#ifdef _WIN32
217
FileIo::FileIo(const std::wstring& path) : p_(std::make_unique<Impl>(path)) {
218
}
219
#endif
220
221
35.5k
FileIo::~FileIo() {
222
35.5k
  close();
223
35.5k
}
224
225
215k
int FileIo::munmap() {
226
215k
  int rc = 0;
227
215k
  if (p_->pMappedArea_) {
228
#ifdef _WIN32
229
    UnmapViewOfFile(p_->pMappedArea_);
230
    CloseHandle(p_->hMap_);
231
    p_->hMap_ = nullptr;
232
    CloseHandle(p_->hFile_);
233
    p_->hFile_ = nullptr;
234
#elif __has_include(<sys/mman.h>)
235
2.71k
    if (::munmap(p_->pMappedArea_, p_->mappedLength_) != 0) {
236
0
      rc = 1;
237
0
    }
238
#else
239
#error Platforms without mmap are not supported. See https://github.com/Exiv2/exiv2/issues/2380
240
    if (p_->isWriteable_) {
241
      seek(0, BasicIo::beg);
242
      write(p_->pMappedArea_, p_->mappedLength_);
243
    }
244
    delete[] p_->pMappedArea_;
245
#endif
246
2.71k
  }
247
215k
  if (p_->isWriteable_) {
248
0
    if (p_->fp_)
249
0
      p_->switchMode(Impl::opRead);
250
0
    p_->isWriteable_ = false;
251
0
  }
252
215k
  p_->pMappedArea_ = nullptr;
253
215k
  p_->mappedLength_ = 0;
254
215k
  return rc;
255
215k
}
256
257
2.71k
byte* FileIo::mmap(bool isWriteable) {
258
2.71k
  if (munmap() != 0) {
259
0
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "munmap");
260
0
  }
261
2.71k
  p_->mappedLength_ = size();
262
2.71k
  p_->isWriteable_ = isWriteable;
263
2.71k
  if (p_->isWriteable_ && p_->switchMode(Impl::opWrite) != 0) {
264
0
    throw Error(ErrorCode::kerFailedToMapFileForReadWrite, path(), strError());
265
0
  }
266
2.71k
#if __has_include(<sys/mman.h>)
267
2.71k
  int prot = PROT_READ;
268
2.71k
  if (p_->isWriteable_) {
269
0
    prot |= PROT_WRITE;
270
0
  }
271
2.71k
  void* rc = ::mmap(nullptr, p_->mappedLength_, prot, MAP_SHARED, _fileno(p_->fp_), 0);
272
2.71k
  if (MAP_FAILED == rc) {
273
0
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "mmap");
274
0
  }
275
2.71k
  p_->pMappedArea_ = static_cast<byte*>(rc);
276
277
#elif defined _WIN32
278
  // Windows implementation
279
280
  // TODO: An attempt to map a file with a length of 0 (zero) fails with
281
  // an error code of ERROR_FILE_INVALID.
282
  // Applications should test for files with a length of 0 (zero) and
283
  // reject those files.
284
285
  DWORD dwAccess = FILE_MAP_READ;
286
  DWORD flProtect = PAGE_READONLY;
287
  if (isWriteable) {
288
    dwAccess = FILE_MAP_WRITE;
289
    flProtect = PAGE_READWRITE;
290
  }
291
  HANDLE hPh = GetCurrentProcess();
292
  auto hFd = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(p_->fp_)));
293
  if (hFd == INVALID_HANDLE_VALUE) {
294
    throw Error(ErrorCode::kerCallFailed, path(), "MSG1", "_get_osfhandle");
295
  }
296
  if (!DuplicateHandle(hPh, hFd, hPh, &p_->hFile_, 0, false, DUPLICATE_SAME_ACCESS)) {
297
    throw Error(ErrorCode::kerCallFailed, path(), "MSG2", "DuplicateHandle");
298
  }
299
  // For the call to CreateFileMapping(), p_->mappedLength_ needs to be split into high and low parts:
300
  ULARGE_INTEGER mappedLength{};
301
  mappedLength.QuadPart = p_->mappedLength_;
302
  p_->hMap_ = CreateFileMapping(p_->hFile_, nullptr, flProtect, mappedLength.HighPart, mappedLength.LowPart, nullptr);
303
  if (p_->hMap_ == nullptr) {
304
    throw Error(ErrorCode::kerCallFailed, path(), "MSG3", "CreateFileMapping");
305
  }
306
  void* rc = MapViewOfFile(p_->hMap_, dwAccess, 0, 0, 0);
307
  if (rc == nullptr) {
308
    throw Error(ErrorCode::kerCallFailed, path(), "MSG4", "CreateFileMapping");
309
  }
310
  p_->pMappedArea_ = static_cast<byte*>(rc);
311
#else
312
#error Platforms without mmap are not supported. See https://github.com/Exiv2/exiv2/issues/2380
313
  // Workaround for platforms without mmap: Read the file into memory
314
  byte* buf = new byte[p_->mappedLength_];
315
  const long offset = std::ftell(p_->fp_);
316
  std::fseek(p_->fp_, 0, SEEK_SET);
317
  if (read(buf, p_->mappedLength_) != p_->mappedLength_) {
318
    delete[] buf;
319
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "FileIo::read");
320
  }
321
  std::fseek(p_->fp_, offset, SEEK_SET);
322
  if (error()) {
323
    delete[] buf;
324
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "FileIo::mmap");
325
  }
326
  p_->pMappedArea_ = buf;
327
#endif
328
2.71k
  return p_->pMappedArea_;
329
2.71k
}
330
331
0
void FileIo::setPath(const std::string& path) {
332
0
  close();
333
0
  p_->path_ = path;
334
#ifdef _WIN32
335
  wchar_t t[512];
336
  const auto nw = MultiByteToWideChar(CP_UTF8, 0, p_->path_.data(), static_cast<int>(p_->path_.size()), t, 512);
337
  p_->wpath_.assign(t, nw);
338
#endif
339
0
}
340
341
#ifdef _WIN32
342
void FileIo::setPath(const std::wstring& path) {
343
  close();
344
  p_->wpath_ = path;
345
  char t[1024];
346
  const auto nc = WideCharToMultiByte(CP_UTF8, 0, p_->wpath_.data(), static_cast<int>(p_->wpath_.size()), t, 1024,
347
                                      nullptr, nullptr);
348
  p_->path_.assign(t, nc);
349
}
350
#endif
351
352
0
size_t FileIo::write(const byte* data, size_t wcount) {
353
0
  if (p_->switchMode(Impl::opWrite) != 0)
354
0
    return 0;
355
0
  return std::fwrite(data, 1, wcount, p_->fp_);
356
0
}
357
358
0
size_t FileIo::write(BasicIo& src) {
359
0
  if (static_cast<BasicIo*>(this) == &src)
360
0
    return 0;
361
0
  if (!src.isopen())
362
0
    return 0;
363
0
  if (p_->switchMode(Impl::opWrite) != 0)
364
0
    return 0;
365
366
0
  byte buf[4096];
367
0
  size_t writeTotal = 0;
368
0
  size_t readCount = src.read(buf, sizeof(buf));
369
0
  while (readCount != 0) {
370
0
    size_t writeCount = std::fwrite(buf, 1, readCount, p_->fp_);
371
0
    writeTotal += writeCount;
372
0
    if (writeCount != readCount) {
373
      // try to reset back to where write stopped
374
0
      src.seek(writeCount - readCount, BasicIo::cur);
375
0
      break;
376
0
    }
377
0
    readCount = src.read(buf, sizeof(buf));
378
0
  }
379
380
0
  return writeTotal;
381
0
}
382
383
0
void FileIo::transfer(BasicIo& src) {
384
0
  const bool wasOpen = (p_->fp_ != nullptr);
385
0
  const std::string lastMode(p_->openMode_);
386
387
0
  if (auto fileIo = dynamic_cast<FileIo*>(&src)) {
388
    // Optimization if src is another instance of FileIo
389
0
    fileIo->close();
390
    // Check if the file can be written to, if it already exists
391
0
    if (open("a+b") != 0) {
392
      // Remove the (temporary) file
393
0
      fs::remove(fileIo->path());
394
0
      throw Error(ErrorCode::kerFileOpenFailed, path(), "a+b", strError());
395
0
    }
396
0
    close();
397
398
0
    bool statOk = true;
399
0
    fs::perms origStMode;
400
0
    const auto& pf = path();
401
402
0
    Impl::StructStat buf1;
403
0
    if (p_->stat(buf1) == -1) {
404
0
      statOk = false;
405
0
    }
406
0
    origStMode = buf1.st_mode;
407
408
0
    {
409
#if defined(_WIN32) && defined(REPLACEFILE_IGNORE_MERGE_ERRORS)
410
      // Windows implementation that deals with the fact that ::rename fails
411
      // if the target filename still exists, which regularly happens when
412
      // that file has been opened with FILE_SHARE_DELETE by another process,
413
      // like a virus scanner or disk indexer
414
      // (see also http://stackoverflow.com/a/11023068)
415
      auto ret =
416
          ReplaceFileA(pf.c_str(), fileIo->path().c_str(), nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr);
417
      if (ret == 0) {
418
        if (GetLastError() != ERROR_FILE_NOT_FOUND)
419
          throw Error(ErrorCode::kerFileRenameFailed, fileIo->path(), pf, strError());
420
        fs::rename(fileIo->path(), pf);
421
        fs::remove(fileIo->path());
422
      } else {
423
        if (fileExists(pf) && fs::remove(pf) != 0)
424
          throw Error(ErrorCode::kerCallFailed, pf, strError(), "fs::remove");
425
        fs::rename(fileIo->path(), pf);
426
        fs::remove(fileIo->path());
427
      }
428
#else
429
0
      if (fileExists(pf) && fs::remove(pf) != 0) {
430
0
        throw Error(ErrorCode::kerCallFailed, pf, strError(), "fs::remove");
431
0
      }
432
0
      fs::rename(fileIo->path(), pf);
433
0
      fs::remove(fileIo->path());
434
0
#endif
435
      // Check permissions of new file
436
0
      auto newStMode = fs::status(pf).permissions();
437
      // Set original file permissions
438
0
      if (statOk && origStMode != newStMode) {
439
0
        fs::permissions(pf, origStMode);
440
0
#ifndef SUPPRESS_WARNINGS
441
0
        EXV_WARNING << Error(ErrorCode::kerCallFailed, pf, strError(), "::chmod") << "\n";
442
0
#endif
443
0
      }
444
0
    }
445
0
  }  // if (fileIo)
446
0
  else {
447
    // Generic handling, reopen both to reset to start
448
0
    if (open("w+b") != 0) {
449
0
      throw Error(ErrorCode::kerFileOpenFailed, path(), "w+b", strError());
450
0
    }
451
0
    if (src.open() != 0) {
452
0
      throw Error(ErrorCode::kerDataSourceOpenFailed, src.path(), strError());
453
0
    }
454
0
    write(src);
455
0
    src.close();
456
0
  }
457
458
0
  if (wasOpen) {
459
0
    if (open(lastMode) != 0) {
460
0
      throw Error(ErrorCode::kerFileOpenFailed, path(), lastMode, strError());
461
0
    }
462
0
  } else
463
0
    close();
464
465
0
  if (error() || src.error()) {
466
0
    throw Error(ErrorCode::kerTransferFailed, path(), strError());
467
0
  }
468
0
}  // FileIo::transfer
469
470
0
int FileIo::putb(byte data) {
471
0
  if (p_->switchMode(Impl::opWrite) != 0)
472
0
    return EOF;
473
0
  return putc(data, p_->fp_);
474
0
}
475
476
365k
int FileIo::seek(int64_t offset, Position pos) {
477
365k
  int fileSeek = 0;
478
365k
  switch (pos) {
479
263k
    case BasicIo::cur:
480
263k
      fileSeek = SEEK_CUR;
481
263k
      break;
482
102k
    case BasicIo::beg:
483
102k
      fileSeek = SEEK_SET;
484
102k
      break;
485
0
    case BasicIo::end:
486
0
      fileSeek = SEEK_END;
487
0
      break;
488
365k
  }
489
490
365k
  if (p_->switchMode(Impl::opSeek) != 0)
491
0
    return 1;
492
#ifdef _WIN32
493
  return _fseeki64(p_->fp_, offset, fileSeek);
494
#else
495
365k
  return fseeko(p_->fp_, offset, fileSeek);
496
365k
#endif
497
365k
}
498
499
410k
size_t FileIo::tell() const {
500
#ifdef _WIN32
501
  auto pos = _ftelli64(p_->fp_);
502
#else
503
410k
  auto pos = ftello(p_->fp_);
504
410k
#endif
505
410k
  Internal::enforce(pos >= 0, ErrorCode::kerInputDataReadFailed);
506
410k
  return static_cast<size_t>(pos);
507
410k
}
508
509
274k
size_t FileIo::size() const {
510
  // Flush and commit only if the file is open for writing
511
274k
  if (p_->fp_ && (p_->openMode_.front() != 'r' || p_->openMode_.at(1) == '+')) {
512
0
    std::fflush(p_->fp_);
513
#ifdef _MSC_VER
514
    // This is required on msvcrt before stat after writing to a file
515
    _commit(_fileno(p_->fp_));
516
#endif
517
0
  }
518
519
274k
  Impl::StructStat buf;
520
274k
  if (p_->stat(buf))
521
0
    return std::numeric_limits<size_t>::max();
522
274k
  return static_cast<size_t>(buf.st_size);
523
274k
}
524
525
106k
int FileIo::open() {
526
  // Default open is in read-only binary mode
527
106k
  return open("rb");
528
106k
}
529
530
106k
int FileIo::open(const std::string& mode) {
531
106k
  close();
532
106k
  p_->openMode_ = mode;
533
106k
  p_->opMode_ = Impl::opSeek;
534
#ifdef _WIN32
535
  wchar_t wmode[10];
536
  MultiByteToWideChar(CP_UTF8, 0, mode.c_str(), -1, wmode, 10);
537
  if (_wfopen_s(&p_->fp_, p_->wpath_.c_str(), wmode))
538
    return 1;
539
#else
540
106k
  p_->fp_ = ::fopen(path().c_str(), mode.c_str());
541
106k
  if (!p_->fp_)
542
0
    return 1;
543
106k
#endif
544
106k
  return 0;
545
106k
}
546
547
70.9k
bool FileIo::isopen() const {
548
70.9k
  return p_->fp_ != nullptr;
549
70.9k
}
550
551
212k
int FileIo::close() {
552
212k
  int rc = 0;
553
212k
  if (munmap() != 0)
554
0
    rc = 2;
555
212k
  if (p_->fp_) {
556
106k
    if (std::fclose(p_->fp_) != 0)
557
0
      rc |= 1;
558
106k
    p_->fp_ = nullptr;
559
106k
  }
560
212k
  return rc;
561
212k
}
562
563
56.8k
DataBuf FileIo::read(size_t rcount) {
564
56.8k
  if (rcount > size())
565
0
    throw Error(ErrorCode::kerInvalidMalloc);
566
56.8k
  DataBuf buf(rcount);
567
56.8k
  size_t readCount = read(buf.data(), buf.size());
568
56.8k
  if (readCount == 0) {
569
0
    throw Error(ErrorCode::kerInputDataReadFailed);
570
0
  }
571
56.8k
  buf.resize(readCount);
572
56.8k
  return buf;
573
56.8k
}
574
575
732k
size_t FileIo::read(byte* buf, size_t rcount) {
576
732k
  if (p_->switchMode(Impl::opRead) != 0) {
577
0
    return 0;
578
0
  }
579
732k
  return std::fread(buf, 1, rcount, p_->fp_);
580
732k
}
581
582
7.81M
int FileIo::getb() {
583
7.81M
  if (p_->switchMode(Impl::opRead) != 0)
584
0
    return EOF;
585
7.81M
  return getc(p_->fp_);
586
7.81M
}
587
588
720k
int FileIo::error() const {
589
720k
  return p_->fp_ ? ferror(p_->fp_) : 0;
590
720k
}
591
592
459k
bool FileIo::eof() const {
593
459k
  return std::feof(p_->fp_) != 0;
594
459k
}
595
596
108k
const std::string& FileIo::path() const noexcept {
597
108k
  return p_->path_;
598
108k
}
599
600
0
void FileIo::populateFakeData() {
601
0
}
602
#endif
603
604
//! Internal Pimpl structure of class MemIo.
605
class MemIo::Impl final {
606
 public:
607
0
  Impl() = default;                     //!< Default constructor
608
  Impl(const byte* data, size_t size);  //!< Constructor 2
609
  ~Impl() = default;
610
611
  // DATA
612
  byte* data_{nullptr};     //!< Pointer to the start of the memory area
613
  size_t idx_{0};           //!< Index into the memory area
614
  size_t size_{0};          //!< Size of the memory area
615
  size_t sizeAlloced_{0};   //!< Size of the allocated buffer
616
  bool isMalloced_{false};  //!< Was the buffer allocated?
617
  bool eof_{false};         //!< EOF indicator
618
619
  // METHODS
620
  void reserve(size_t wcount);  //!< Reserve memory
621
622
  // NOT IMPLEMENTED
623
  Impl(const Impl&) = delete;             //!< Copy constructor
624
  Impl& operator=(const Impl&) = delete;  //!< Assignment
625
};
626
627
43
MemIo::Impl::Impl(const byte* data, size_t size) : data_(const_cast<byte*>(data)), size_(size) {
628
43
}
629
630
/*!
631
  @brief Utility class provides the block mapping to the part of data. This avoids allocating
632
        a single contiguous block of memory to the big data.
633
 */
634
class BlockMap {
635
 public:
636
  //! the status of the block.
637
  enum blockType_e { bNone, bKnown, bMemory };
638
639
  //! @brief Populate the block.
640
  //! @param source The data populate to the block
641
  //! @param num The size of data
642
0
  void populate(const byte* source, size_t num) {
643
0
    size_ = num;
644
0
    data_ = Blob(source, source + num);
645
0
    type_ = bMemory;
646
0
  }
647
648
  /*!
649
    @brief Change the status to bKnow. bKnow blocks do not contain the data,
650
          but they keep the size of data. This avoids allocating memory for parts
651
          of the file that contain image-date (non-metadata/pixel data) which never change in exiv2.
652
    @param num The size of the data
653
   */
654
0
  void markKnown(size_t num) {
655
0
    type_ = bKnown;
656
0
    size_ = num;
657
0
  }
658
659
0
  [[nodiscard]] bool isNone() const {
660
0
    return type_ == bNone;
661
0
  }
662
663
0
  [[nodiscard]] bool isKnown() const {
664
0
    return type_ == bKnown;
665
0
  }
666
667
0
  [[nodiscard]] auto getData() const {
668
0
    return data_.data();
669
0
  }
670
671
0
  [[nodiscard]] size_t getSize() const {
672
0
    return size_;
673
0
  }
674
675
 private:
676
  blockType_e type_{bNone};
677
  Blob data_;
678
  size_t size_{};
679
};
680
681
0
void MemIo::Impl::reserve(size_t wcount) {
682
0
  const size_t need = wcount + idx_;
683
0
  size_t blockSize = 32 * 1024;  // 32768
684
0
  const size_t maxBlockSize = 4 * 1024 * 1024;
685
686
0
  if (!isMalloced_) {
687
    // Minimum size for 1st block
688
0
    auto size = std::max<size_t>(blockSize * (1 + need / blockSize), size_);
689
0
    auto data = static_cast<byte*>(std::malloc(size));
690
0
    if (!data) {
691
0
      throw Error(ErrorCode::kerMallocFailed);
692
0
    }
693
0
    if (data_) {
694
0
      std::memcpy(data, data_, size_);
695
0
    }
696
0
    data_ = data;
697
0
    sizeAlloced_ = size;
698
0
    isMalloced_ = true;
699
0
  }
700
701
0
  if (need > size_) {
702
0
    if (need > sizeAlloced_) {
703
0
      blockSize = std::min(2 * sizeAlloced_, maxBlockSize);
704
      // Allocate in blocks
705
0
      size_t want = blockSize * (1 + need / blockSize);
706
0
      data_ = static_cast<byte*>(std::realloc(data_, want));
707
0
      if (!data_) {
708
0
        throw Error(ErrorCode::kerMallocFailed);
709
0
      }
710
0
      sizeAlloced_ = want;
711
0
    }
712
0
    size_ = need;
713
0
  }
714
0
}
715
716
0
MemIo::MemIo() : p_(std::make_unique<Impl>()) {
717
0
}
718
719
43
MemIo::MemIo(const byte* data, size_t size) : p_(std::make_unique<Impl>(data, size)) {
720
43
}
721
722
43
MemIo::~MemIo() {
723
43
  if (p_->isMalloced_) {
724
0
    std::free(p_->data_);
725
0
  }
726
43
}
727
728
0
size_t MemIo::write(const byte* data, size_t wcount) {
729
0
  p_->reserve(wcount);
730
0
  if (data) {
731
0
    std::memcpy(&p_->data_[p_->idx_], data, wcount);
732
0
  }
733
0
  p_->idx_ += wcount;
734
0
  return wcount;
735
0
}
736
737
0
void MemIo::transfer(BasicIo& src) {
738
0
  if (auto memIo = dynamic_cast<MemIo*>(&src)) {
739
    // Optimization if src is another instance of MemIo
740
0
    if (p_->isMalloced_) {
741
0
      std::free(p_->data_);
742
0
    }
743
0
    p_->idx_ = 0;
744
0
    p_->data_ = memIo->p_->data_;
745
0
    p_->size_ = memIo->p_->size_;
746
0
    p_->isMalloced_ = memIo->p_->isMalloced_;
747
0
    memIo->p_->idx_ = 0;
748
0
    memIo->p_->data_ = nullptr;
749
0
    memIo->p_->size_ = 0;
750
0
    memIo->p_->isMalloced_ = false;
751
0
  } else {
752
    // Generic reopen to reset position to start
753
0
    if (src.open() != 0) {
754
0
      throw Error(ErrorCode::kerDataSourceOpenFailed, src.path(), strError());
755
0
    }
756
0
    p_->idx_ = 0;
757
0
    write(src);
758
0
    src.close();
759
0
  }
760
0
  if (error() || src.error())
761
0
    throw Error(ErrorCode::kerMemoryTransferFailed, strError());
762
0
}
763
764
0
size_t MemIo::write(BasicIo& src) {
765
0
  if (this == &src)
766
0
    return 0;
767
0
  if (!src.isopen())
768
0
    return 0;
769
770
0
  byte buf[4096];
771
0
  size_t writeTotal = 0;
772
0
  size_t readCount = src.read(buf, sizeof(buf));
773
0
  while (readCount != 0) {
774
0
    write(buf, readCount);
775
0
    writeTotal += readCount;
776
0
    readCount = src.read(buf, sizeof(buf));
777
0
  }
778
779
0
  return writeTotal;
780
0
}
781
782
0
int MemIo::putb(byte data) {
783
0
  p_->reserve(1);
784
0
  p_->data_[p_->idx_++] = data;
785
0
  return data;
786
0
}
787
788
592
int MemIo::seek(int64_t offset, Position pos) {
789
592
  int64_t newIdx = 0;
790
791
592
  switch (pos) {
792
493
    case BasicIo::cur:
793
493
      newIdx = p_->idx_ + offset;
794
493
      break;
795
99
    case BasicIo::beg:
796
99
      newIdx = offset;
797
99
      break;
798
0
    case BasicIo::end:
799
0
      newIdx = p_->size_ + offset;
800
0
      break;
801
592
  }
802
803
592
  if (newIdx < 0)
804
0
    return 1;
805
806
592
  if (newIdx > static_cast<int64_t>(p_->size_)) {
807
1
    p_->eof_ = true;
808
1
    return 1;
809
1
  }
810
811
591
  p_->idx_ = static_cast<size_t>(newIdx);
812
591
  p_->eof_ = false;
813
591
  return 0;
814
592
}
815
816
2
byte* MemIo::mmap(bool /*isWriteable*/) {
817
2
  return p_->data_;
818
2
}
819
820
0
int MemIo::munmap() {
821
0
  return 0;
822
0
}
823
824
40
size_t MemIo::tell() const {
825
40
  return p_->idx_;
826
40
}
827
828
27
size_t MemIo::size() const {
829
27
  return p_->size_;
830
27
}
831
832
59
int MemIo::open() {
833
59
  p_->idx_ = 0;
834
59
  p_->eof_ = false;
835
59
  return 0;
836
59
}
837
838
38
bool MemIo::isopen() const {
839
38
  return true;
840
38
}
841
842
38
int MemIo::close() {
843
38
  return 0;
844
38
}
845
846
22
DataBuf MemIo::read(size_t rcount) {
847
22
  DataBuf buf(rcount);
848
22
  size_t readCount = read(buf.data(), buf.size());
849
22
  buf.resize(readCount);
850
22
  return buf;
851
22
}
852
853
699
size_t MemIo::read(byte* buf, size_t rcount) {
854
699
  const auto avail = std::max<size_t>(p_->size_ - p_->idx_, 0);
855
699
  const auto allow = std::min<size_t>(rcount, avail);
856
699
  if (allow > 0) {
857
694
    std::memcpy(buf, &p_->data_[p_->idx_], allow);
858
694
  }
859
699
  p_->idx_ += allow;
860
699
  if (rcount > avail) {
861
1
    p_->eof_ = true;
862
1
  }
863
699
  return allow;
864
699
}
865
866
139
int MemIo::getb() {
867
139
  if (p_->idx_ >= p_->size_) {
868
0
    p_->eof_ = true;
869
0
    return EOF;
870
0
  }
871
139
  return p_->data_[p_->idx_++];
872
139
}
873
874
729
int MemIo::error() const {
875
729
  return 0;
876
729
}
877
878
639
bool MemIo::eof() const {
879
639
  return p_->eof_;
880
639
}
881
882
18
const std::string& MemIo::path() const noexcept {
883
18
  static std::string _path{"MemIo"};
884
18
  return _path;
885
18
}
886
887
0
void MemIo::populateFakeData() {
888
0
}
889
890
#ifdef EXV_ENABLE_FILESYSTEM
891
0
XPathIo::XPathIo(const std::string& orgPath) : FileIo(XPathIo::writeDataToFile(orgPath)), tempFilePath_(path()) {
892
0
}
893
894
0
XPathIo::~XPathIo() {
895
0
  if (isTemp_ && !fs::remove(tempFilePath_)) {
896
    // error when removing file
897
    // printf ("Warning: Unable to remove the temp file %s.\n", tempFilePath_.c_str());
898
0
  }
899
0
}
900
901
0
void XPathIo::transfer(BasicIo& src) {
902
0
  if (isTemp_) {
903
    // replace temp path to gent path.
904
0
    auto currentPath = path();
905
906
    // replace each substring of the subject that matches the given search string with the given replacement.
907
0
    auto ReplaceStringInPlace = [](std::string& subject, std::string_view search, std::string_view replace) {
908
0
      auto pos = subject.find(search);
909
0
      while (pos != std::string::npos) {
910
0
        subject.replace(pos, search.length(), replace);
911
0
        pos += subject.find(search, pos + replace.length());
912
0
      }
913
0
    };
914
915
0
    ReplaceStringInPlace(currentPath, XPathIo::TEMP_FILE_EXT, XPathIo::GEN_FILE_EXT);
916
0
    setPath(currentPath);
917
918
0
    tempFilePath_ = path();
919
0
    fs::rename(currentPath, tempFilePath_);
920
0
    isTemp_ = false;
921
    // call super class method
922
0
    FileIo::transfer(src);
923
0
  }
924
0
}
925
926
0
std::string XPathIo::writeDataToFile(const std::string& orgPath) {
927
0
  Protocol prot = fileProtocol(orgPath);
928
929
  // generating the name for temp file.
930
0
  std::time_t timestamp = std::time(nullptr);
931
0
  auto path = stringFormat("{}{}", timestamp, XPathIo::TEMP_FILE_EXT);
932
933
0
  if (prot == pStdin) {
934
0
    if (_isatty(_fileno(stdin)))
935
0
      throw Error(ErrorCode::kerInputDataReadFailed);
936
#ifdef _WIN32
937
    // convert stdin to binary
938
    if (_setmode(_fileno(stdin), _O_BINARY) == -1)
939
      throw Error(ErrorCode::kerInputDataReadFailed);
940
#endif
941
0
    std::ofstream fs(path, std::ios::out | std::ios::binary | std::ios::trunc);
942
    // read stdin and write to the temp file.
943
0
    auto readBuf = std::make_unique<char[]>(100 * 1024);
944
0
    std::streamsize readBufSize = 0;
945
0
    do {
946
0
      std::cin.read(readBuf.get(), 100 * 1024);
947
0
      readBufSize = std::cin.gcount();
948
0
      if (readBufSize > 0) {
949
0
        fs.write(readBuf.get(), readBufSize);
950
0
      }
951
0
    } while (readBufSize);
952
0
    fs.close();
953
0
  } else if (prot == pDataUri) {
954
0
    std::ofstream fs(path, std::ios::out | std::ios::binary | std::ios::trunc);
955
    // read data uri and write to the temp file.
956
0
    size_t base64Pos = orgPath.find("base64,");
957
0
    if (base64Pos == std::string::npos) {
958
0
      fs.close();
959
0
      throw Error(ErrorCode::kerErrorMessage, "No base64 data");
960
0
    }
961
962
0
    std::string data = orgPath.substr(base64Pos + 7);
963
0
    auto decodeData = std::make_unique<char[]>(data.length());
964
0
    auto size = base64decode(data.c_str(), decodeData.get(), data.length());
965
0
    if (size > 0) {
966
0
      fs.write(decodeData.get(), size);
967
0
      fs.close();
968
0
    } else {
969
0
      fs.close();
970
0
      throw Error(ErrorCode::kerErrorMessage, "Unable to decode base 64.");
971
0
    }
972
0
  }
973
974
0
  return path;
975
0
}
976
977
#endif
978
979
//! Internal Pimpl abstract structure of class RemoteIo.
980
class RemoteIo::Impl {
981
 public:
982
  //! Constructor
983
  Impl(const std::string& url, size_t blockSize);
984
  //! Destructor. Releases all managed memory.
985
0
  virtual ~Impl() = default;
986
987
  // DATA
988
  std::string path_;                 //!< (Standard) path
989
  size_t blockSize_;                 //!< Size of the block memory.
990
  std::vector<BlockMap> blocksMap_;  //!< An array contains all blocksMap
991
  size_t size_{0};                   //!< The file size
992
  size_t idx_{0};                    //!< Index into the memory area
993
  bool isOpen_{false};               //!< Is the IO open?
994
  bool eof_{false};                  //!< EOF indicator
995
  Protocol protocol_;                //!< the protocol of url
996
  size_t totalRead_{0};              //!< total number of bytes read from host
997
998
  // METHODS
999
  /*!
1000
    @brief Get the length (in bytes) of the remote file.
1001
    @return Return -1 if the size is unknown. Otherwise it returns the length of remote file (in bytes).
1002
    @throw Error if the server returns the error code.
1003
   */
1004
  [[nodiscard]] virtual int64_t getFileLength() const = 0;
1005
  /*!
1006
    @brief Get the data by range.
1007
    @param startBlock The start block index.
1008
    @param stopBlock The stop block index.
1009
    @param response The data from the server.
1010
    @throw Error if the server returns the error code.
1011
    @note Set startBlock = -1 and stopBlock = -1 to get the whole file content.
1012
   */
1013
  virtual void getDataByRange(size_t startBlock, size_t stopBlock, std::string& response) const = 0;
1014
  /*!
1015
    @brief Submit the data to the remote machine. The data replace a part of the remote file.
1016
          The replaced part of remote file is indicated by from and to parameters.
1017
    @param data The data are submitted to the remote machine.
1018
    @param size The size of data.
1019
    @param from The start position in the remote file where the data replace.
1020
    @param to The end position in the remote file where the data replace.
1021
    @note The write access is available on some protocols. HTTP and HTTPS require the script file
1022
          on the remote machine to handle the data. SSH requires the permission to edit the file.
1023
    @throw Error if it fails.
1024
   */
1025
  virtual void writeRemote(const byte* data, size_t size, size_t from, size_t to) = 0;
1026
  /*!
1027
    @brief Get the data from the remote machine and write them to the memory blocks.
1028
    @param startBlock The start block index.
1029
    @param stopBlock The stop block index.
1030
    @return Number of bytes written to the memory block successfully
1031
    @throw Error if it fails.
1032
   */
1033
  virtual size_t populateBlocks(size_t startBlock, size_t stopBlock);
1034
};
1035
1036
RemoteIo::Impl::Impl(const std::string& url, size_t blockSize) :
1037
0
    path_(url), blockSize_(blockSize), protocol_(fileProtocol(url)) {
1038
0
}
1039
1040
0
size_t RemoteIo::Impl::populateBlocks(size_t startBlock, size_t stopBlock) {
1041
  // optimize: ignore all true blocks on left & right sides.
1042
0
  while (startBlock < stopBlock && !blocksMap_.at(startBlock).isNone())
1043
0
    startBlock++;
1044
0
  while (startBlock < stopBlock && !blocksMap_.at(stopBlock - 1).isNone())
1045
0
    stopBlock--;
1046
0
  if (startBlock >= stopBlock) {
1047
0
    return 0;
1048
0
  }
1049
1050
0
  size_t rcount = 0;
1051
0
  std::string data;
1052
0
  getDataByRange(startBlock, stopBlock, data);
1053
0
  rcount = data.length();
1054
0
  size_t iBlock;
1055
0
  size_t iStop;
1056
0
  if (rcount == 0) {
1057
0
    throw Error(ErrorCode::kerErrorMessage, "Data By Range is empty. Please check the permission.");
1058
0
  } else if (rcount > size_) {
1059
0
    throw Error(ErrorCode::kerErrorMessage, "Remote server returned more bytes than the specified file size.");
1060
0
  } else if (rcount == size_) {
1061
    // The remote server is returning the entire file, rather than the subset that we asked for.
1062
0
    iBlock = 0;
1063
0
    iStop = (size_ + blockSize_ - 1) / blockSize_;
1064
0
  } else {
1065
0
    iBlock = startBlock;
1066
0
    iStop = stopBlock;
1067
0
  }
1068
1069
0
  auto source = reinterpret_cast<byte*>(const_cast<char*>(data.c_str()));
1070
0
  size_t remain = rcount;
1071
0
  size_t totalRead = 0;
1072
1073
0
  while (iBlock < iStop && remain) {
1074
0
    auto allow = std::min<size_t>(remain, blockSize_);
1075
0
    blocksMap_.at(iBlock).populate(&source[totalRead], allow);
1076
0
    remain -= allow;
1077
0
    totalRead += allow;
1078
0
    iBlock++;
1079
0
  }
1080
1081
0
  return rcount;
1082
0
}
1083
1084
0
RemoteIo::RemoteIo() = default;
1085
1086
0
RemoteIo::~RemoteIo() {
1087
0
  if (p_) {
1088
0
    close();
1089
0
  }
1090
0
}
1091
1092
0
int RemoteIo::open() {
1093
0
  close();  // reset the IO position
1094
0
  bigBlock_ = nullptr;
1095
0
  if (!p_->isOpen_) {
1096
0
    const auto length = p_->getFileLength();
1097
0
    if (length < 0) {  // unable to get the length of remote file, get the whole file content.
1098
0
      std::string data;
1099
0
      p_->getDataByRange(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max(), data);
1100
0
      p_->size_ = data.length();
1101
0
      size_t nBlocks = (p_->size_ + p_->blockSize_ - 1) / p_->blockSize_;
1102
0
      p_->blocksMap_.resize(nBlocks);
1103
0
      p_->isOpen_ = true;
1104
0
      auto source = reinterpret_cast<const byte*>(data.c_str());
1105
0
      size_t remain = p_->size_;
1106
0
      size_t iBlock = 0;
1107
0
      size_t totalRead = 0;
1108
0
      while (remain) {
1109
0
        auto allow = std::min<size_t>(remain, p_->blockSize_);
1110
0
        p_->blocksMap_.at(iBlock).populate(&source[totalRead], allow);
1111
0
        remain -= allow;
1112
0
        totalRead += allow;
1113
0
        iBlock++;
1114
0
      }
1115
0
    } else if (length == 0) {  // file is empty
1116
0
      throw Error(ErrorCode::kerErrorMessage, "the file length is 0");
1117
0
    } else if (length > MAX_REMOTE_FILE_SIZE) {
1118
0
      throw Error(ErrorCode::kerErrorMessage, "the remote file is too large");
1119
0
    } else {
1120
0
      p_->size_ = static_cast<size_t>(length);
1121
0
      size_t nBlocks = (p_->size_ + p_->blockSize_ - 1) / p_->blockSize_;
1122
0
      p_->blocksMap_.resize(nBlocks);
1123
0
      p_->isOpen_ = true;
1124
0
    }
1125
0
  }
1126
0
  return 0;  // means OK
1127
0
}
1128
1129
0
int RemoteIo::close() {
1130
0
  if (p_->isOpen_) {
1131
0
    p_->eof_ = false;
1132
0
    p_->idx_ = 0;
1133
0
    p_->isOpen_ = false;
1134
0
  }
1135
#ifdef EXIV2_DEBUG_MESSAGES
1136
  std::cerr << "RemoteIo::close totalRead_ = " << p_->totalRead_ << '\n';
1137
#endif
1138
0
  if (bigBlock_) {
1139
0
    delete[] bigBlock_;
1140
0
    bigBlock_ = nullptr;
1141
0
  }
1142
0
  return 0;
1143
0
}
1144
1145
0
size_t RemoteIo::write(const byte* /* unused data*/, size_t /* unused wcount*/) {
1146
0
  return 0;  // means failure
1147
0
}
1148
1149
0
size_t RemoteIo::write(BasicIo& src) {
1150
0
  if (!src.isopen())
1151
0
    return 0;
1152
1153
  /*
1154
   * The idea is to compare the file content, find the different bytes and submit them to the remote machine.
1155
   * To simplify it, it:
1156
   *      + goes from the left, find the first different position -> $left
1157
   *      + goes from the right, find the first different position -> $right
1158
   * The different bytes are [$left-$right] part.
1159
   */
1160
0
  size_t left = 0;
1161
0
  size_t right = 0;
1162
0
  size_t blockIndex = 0;
1163
0
  auto buf = std::make_unique<byte[]>(p_->blockSize_);
1164
0
  size_t nBlocks = (p_->size_ + p_->blockSize_ - 1) / p_->blockSize_;
1165
1166
  // find $left
1167
0
  src.seek(0, BasicIo::beg);
1168
0
  bool findDiff = false;
1169
0
  while (blockIndex < nBlocks && !src.eof() && !findDiff) {
1170
0
    size_t blockSize = p_->blocksMap_.at(blockIndex).getSize();
1171
0
    bool isFakeData = p_->blocksMap_.at(blockIndex).isKnown();  // fake data
1172
0
    size_t readCount = src.read(buf.get(), blockSize);
1173
0
    auto blockData = p_->blocksMap_.at(blockIndex).getData();
1174
0
    for (size_t i = 0; (i < readCount) && (i < blockSize) && !findDiff; i++) {
1175
0
      if ((!isFakeData && buf[i] != blockData[i]) || (isFakeData && buf[i] != 0)) {
1176
0
        findDiff = true;
1177
0
      } else {
1178
0
        left++;
1179
0
      }
1180
0
    }
1181
0
    blockIndex++;
1182
0
  }
1183
1184
  // find $right
1185
0
  findDiff = false;
1186
0
  blockIndex = nBlocks;
1187
0
  while (blockIndex > 0 && right < src.size() && !findDiff) {
1188
0
    blockIndex--;
1189
0
    size_t blockSize = p_->blocksMap_.at(blockIndex).getSize();
1190
0
    if (src.seek(-1 * (blockSize + right), BasicIo::end)) {
1191
0
      findDiff = true;
1192
0
    } else {
1193
0
      bool isFakeData = p_->blocksMap_.at(blockIndex).isKnown();  // fake data
1194
0
      size_t readCount = src.read(buf.get(), blockSize);
1195
0
      auto blockData = p_->blocksMap_.at(blockIndex).getData();
1196
0
      for (size_t i = 0; (i < readCount) && (i < blockSize) && !findDiff; i++) {
1197
0
        if ((!isFakeData && buf[readCount - i - 1] != blockData[blockSize - i - 1]) ||
1198
0
            (isFakeData && buf[readCount - i - 1] != 0)) {
1199
0
          findDiff = true;
1200
0
        } else {
1201
0
          right++;
1202
0
        }
1203
0
      }
1204
0
    }
1205
0
  }
1206
1207
  // submit to the remote machine.
1208
0
  if (auto dataSize = src.size() - left - right) {
1209
0
    auto data = std::make_unique<byte[]>(dataSize);
1210
0
    src.seek(left, BasicIo::beg);
1211
0
    src.read(data.get(), dataSize);
1212
0
    p_->writeRemote(data.get(), dataSize, left, p_->size_ - right);
1213
0
  }
1214
0
  return src.size();
1215
0
}
1216
1217
0
int RemoteIo::putb(byte /*unused data*/) {
1218
0
  return 0;
1219
0
}
1220
1221
0
DataBuf RemoteIo::read(size_t rcount) {
1222
0
  DataBuf buf(rcount);
1223
0
  size_t readCount = read(buf.data(), buf.size());
1224
0
  if (readCount == 0) {
1225
0
    throw Error(ErrorCode::kerInputDataReadFailed);
1226
0
  }
1227
0
  buf.resize(readCount);
1228
0
  return buf;
1229
0
}
1230
1231
0
size_t RemoteIo::read(byte* buf, size_t rcount) {
1232
0
  if (p_->eof_)
1233
0
    return 0;
1234
1235
0
  auto allow = std::min<size_t>(rcount, (p_->size_ - p_->idx_));
1236
0
  if (allow == 0) {
1237
0
    return 0;
1238
0
  }
1239
0
  size_t startBlock = p_->idx_ / p_->blockSize_;
1240
0
  size_t stopBlock = (p_->idx_ + allow + p_->blockSize_ - 1) / p_->blockSize_;
1241
1242
  // connect to the remote machine & populate the blocks just in time.
1243
0
  p_->populateBlocks(startBlock, stopBlock);
1244
0
  auto fakeData = static_cast<byte*>(std::calloc(p_->blockSize_, sizeof(byte)));
1245
0
  if (!fakeData) {
1246
0
    throw Error(ErrorCode::kerErrorMessage, "Unable to allocate data");
1247
0
  }
1248
1249
0
  size_t iBlock = startBlock;
1250
0
  size_t startPos = p_->idx_ - (startBlock * p_->blockSize_);
1251
0
  size_t totalRead = 0;
1252
0
  do {
1253
0
    auto data = p_->blocksMap_.at(iBlock++).getData();
1254
0
    if (!data)
1255
0
      data = fakeData;
1256
0
    auto blockR = std::min<size_t>(allow, p_->blockSize_ - startPos);
1257
0
    std::memcpy(&buf[totalRead], &data[startPos], blockR);
1258
0
    totalRead += blockR;
1259
0
    startPos = 0;
1260
0
    allow -= blockR;
1261
0
  } while (allow);
1262
1263
0
  std::free(fakeData);
1264
1265
0
  p_->idx_ += totalRead;
1266
0
  p_->eof_ = (p_->idx_ == p_->size_);
1267
0
  p_->totalRead_ += totalRead;
1268
1269
0
  return totalRead;
1270
0
}
1271
1272
0
int RemoteIo::getb() {
1273
0
  if (p_->idx_ == p_->size_) {
1274
0
    p_->eof_ = true;
1275
0
    return EOF;
1276
0
  }
1277
1278
0
  size_t expectedBlock = p_->idx_ / p_->blockSize_;
1279
  // connect to the remote machine & populate the blocks just in time.
1280
0
  p_->populateBlocks(expectedBlock, expectedBlock + 1);
1281
1282
0
  auto data = p_->blocksMap_.at(expectedBlock).getData();
1283
0
  return data[p_->idx_++ - (expectedBlock * p_->blockSize_)];
1284
0
}
1285
1286
0
void RemoteIo::transfer(BasicIo& src) {
1287
0
  if (src.open() != 0) {
1288
0
    throw Error(ErrorCode::kerErrorMessage, "unable to open src when transferring");
1289
0
  }
1290
0
  write(src);
1291
0
  src.close();
1292
0
}
1293
1294
0
int RemoteIo::seek(int64_t offset, Position pos) {
1295
0
  int64_t newIdx = 0;
1296
1297
0
  switch (pos) {
1298
0
    case BasicIo::cur:
1299
0
      newIdx = p_->idx_ + offset;
1300
0
      break;
1301
0
    case BasicIo::beg:
1302
0
      newIdx = offset;
1303
0
      break;
1304
0
    case BasicIo::end:
1305
0
      newIdx = p_->size_ + offset;
1306
0
      break;
1307
0
  }
1308
1309
  // #1198.  Don't return 1 when asked to seek past EOF.  Stay calm and set eof_
1310
  // if (newIdx < 0 || newIdx > (long) p_->size_) return 1;
1311
0
  p_->idx_ = static_cast<size_t>(newIdx);
1312
0
  p_->eof_ = newIdx > static_cast<int64_t>(p_->size_);
1313
0
  p_->idx_ = std::min(p_->idx_, p_->size_);
1314
0
  return 0;
1315
0
}
1316
1317
0
byte* RemoteIo::mmap(bool /*isWriteable*/) {
1318
0
  if (!bigBlock_) {
1319
0
    size_t blockSize = p_->blockSize_;
1320
0
    size_t blocks = (p_->size_ + blockSize - 1) / blockSize;
1321
0
    bigBlock_ = new byte[blocks * blockSize]{};
1322
0
    for (size_t block = 0; block < blocks; block++) {
1323
0
      auto& b = p_->blocksMap_.at(block);
1324
0
      if (!b.isNone()) {
1325
0
        memcpy(bigBlock_ + (block * blockSize), b.getData(), b.getSize());
1326
0
      }
1327
0
    }
1328
0
  }
1329
1330
0
  return bigBlock_;
1331
0
}
1332
1333
0
int RemoteIo::munmap() {
1334
0
  return 0;
1335
0
}
1336
1337
0
size_t RemoteIo::tell() const {
1338
0
  return p_->idx_;
1339
0
}
1340
1341
0
size_t RemoteIo::size() const {
1342
0
  return p_->size_;
1343
0
}
1344
1345
0
bool RemoteIo::isopen() const {
1346
0
  return p_->isOpen_;
1347
0
}
1348
1349
0
int RemoteIo::error() const {
1350
0
  return 0;
1351
0
}
1352
1353
0
bool RemoteIo::eof() const {
1354
0
  return p_->eof_;
1355
0
}
1356
1357
0
const std::string& RemoteIo::path() const noexcept {
1358
0
  return p_->path_;
1359
0
}
1360
1361
0
void RemoteIo::populateFakeData() {
1362
0
  size_t nBlocks = (p_->size_ + p_->blockSize_ - 1) / p_->blockSize_;
1363
0
  for (size_t i = 0; i < nBlocks; i++) {
1364
0
    if (p_->blocksMap_.at(i).isNone())
1365
0
      p_->blocksMap_.at(i).markKnown(p_->blockSize_);
1366
0
  }
1367
0
}
1368
1369
#ifdef EXV_ENABLE_WEBREADY
1370
//! Internal Pimpl structure of class HttpIo.
1371
class HttpIo::HttpImpl : public Impl {
1372
 public:
1373
  //! Constructor
1374
  HttpImpl(const std::string& url, size_t blockSize);
1375
  Exiv2::Uri hostInfo_;  //!< the host information extracted from the path
1376
1377
  // METHODS
1378
  /*!
1379
    @brief Get the length (in bytes) of the remote file.
1380
    @return Return -1 if the size is unknown. Otherwise it returns the length of remote file (in bytes).
1381
    @throw Error if the server returns the error code.
1382
   */
1383
  [[nodiscard]] int64_t getFileLength() const override;
1384
  /*!
1385
    @brief Get the data by range.
1386
    @param startBlock The start block index.
1387
    @param stopBlock The stop block index.
1388
    @param response The data from the server.
1389
    @throw Error if the server returns the error code.
1390
    @note Set startBlock = -1 and stopBlock = -1 to get the whole file content.
1391
   */
1392
  void getDataByRange(size_t startBlock, size_t stopBlock, std::string& response) const override;
1393
  /*!
1394
    @brief Submit the data to the remote machine. The data replace a part of the remote file.
1395
          The replaced part of remote file is indicated by from and to parameters.
1396
    @param data The data are submitted to the remote machine.
1397
    @param size The size of data.
1398
    @param from The start position in the remote file where the data replace.
1399
    @param to The end position in the remote file where the data replace.
1400
    @note The data are submitted to the remote machine via POST. This requires the script file
1401
          on the remote machine to receive the data and edit the remote file. The server-side
1402
          script may be specified with the environment string EXIV2_HTTP_POST. The default value is
1403
          "/exiv2.php". More info is available at http://dev.exiv2.org/wiki/exiv2
1404
    @throw Error if it fails.
1405
   */
1406
  void writeRemote(const byte* data, size_t size, size_t from, size_t to) override;
1407
};
1408
1409
HttpIo::HttpImpl::HttpImpl(const std::string& url, size_t blockSize) : Impl(url, blockSize) {
1410
  hostInfo_ = Exiv2::Uri::Parse(url);
1411
  Exiv2::Uri::Decode(hostInfo_);
1412
}
1413
1414
int64_t HttpIo::HttpImpl::getFileLength() const {
1415
  Exiv2::Dictionary response;
1416
  Exiv2::Dictionary request;
1417
  std::string errors;
1418
  request["server"] = hostInfo_.Host;
1419
  request["page"] = hostInfo_.Path;
1420
  if (!hostInfo_.Port.empty())
1421
    request["port"] = hostInfo_.Port;
1422
  request["verb"] = "HEAD";
1423
  int serverCode = http(request, response, errors);
1424
  if (serverCode < 0 || serverCode >= 400 || !errors.empty()) {
1425
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, hostInfo_.Path);
1426
  }
1427
1428
  auto lengthIter = response.find("Content-Length");
1429
  if (lengthIter == response.end())
1430
    return -1;
1431
  try {
1432
    return std::stoll(lengthIter->second);
1433
  } catch (const std::logic_error&) {
1434
    // the server returned a non-numeric or out-of-range Content-Length; treat the size as unknown
1435
    return -1;
1436
  }
1437
}
1438
1439
void HttpIo::HttpImpl::getDataByRange(size_t startBlock, size_t stopBlock, std::string& response) const {
1440
  Exiv2::Dictionary responseDic;
1441
  Exiv2::Dictionary request;
1442
  request["server"] = hostInfo_.Host;
1443
  request["page"] = hostInfo_.Path;
1444
  if (!hostInfo_.Port.empty())
1445
    request["port"] = hostInfo_.Port;
1446
  request["verb"] = "GET";
1447
  std::string errors;
1448
  if (startBlock != std::numeric_limits<size_t>::max() && stopBlock != std::numeric_limits<size_t>::max()) {
1449
    request["header"] = stringFormat("Range: bytes={}-{}", startBlock * blockSize_, stopBlock * blockSize_ - 1);
1450
  }
1451
1452
  int serverCode = http(request, responseDic, errors);
1453
  if (serverCode < 0 || serverCode >= 400 || !errors.empty()) {
1454
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, hostInfo_.Path);
1455
  }
1456
  response = responseDic["body"];
1457
}
1458
1459
void HttpIo::HttpImpl::writeRemote(const byte* data, size_t size, size_t from, size_t to) {
1460
  std::string scriptPath(getEnv(envHTTPPOST));
1461
  if (scriptPath.empty()) {
1462
    throw Error(ErrorCode::kerErrorMessage,
1463
                "Please set the path of the server script to handle http post data to EXIV2_HTTP_POST "
1464
                "environmental variable.");
1465
  }
1466
1467
  // standardize the path without "/" at the beginning.
1468
  if (scriptPath.find("://") == std::string::npos && scriptPath.front() != '/') {
1469
    scriptPath = "/" + scriptPath;
1470
  }
1471
1472
  Exiv2::Dictionary response;
1473
  Exiv2::Dictionary request;
1474
  std::string errors;
1475
1476
  Uri scriptUri = Exiv2::Uri::Parse(scriptPath);
1477
  request["server"] = scriptUri.Host.empty() ? hostInfo_.Host : scriptUri.Host;
1478
  if (!scriptUri.Port.empty())
1479
    request["port"] = scriptUri.Port;
1480
  request["page"] = scriptUri.Path;
1481
  request["verb"] = "POST";
1482
1483
  // encode base64
1484
  size_t encodeLength = (((size + 2) / 3) * 4) + 1;
1485
  auto encodeData = std::make_unique<char[]>(encodeLength);
1486
  base64encode(data, size, encodeData.get(), encodeLength);
1487
  // url encode
1488
  const std::string urlencodeData = urlencode(encodeData.get());
1489
1490
  auto postData = stringFormat("path={}&from={}&to={}&data={}", hostInfo_.Path, from, to, urlencodeData);
1491
1492
  // create the header
1493
  auto header = stringFormat(
1494
      "Content-Length: {}\n"
1495
      "Content-Type: application/x-www-form-urlencoded\n"
1496
      "\n{}\r\n",
1497
      postData.length(), postData);
1498
  request["header"] = std::move(header);
1499
1500
  int serverCode = http(request, response, errors);
1501
  if (serverCode < 0 || serverCode >= 400 || !errors.empty()) {
1502
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, hostInfo_.Path);
1503
  }
1504
}
1505
1506
HttpIo::HttpIo(const std::string& url, size_t blockSize) {
1507
  p_ = std::make_unique<HttpImpl>(url, blockSize);
1508
}
1509
1510
HttpIo::~HttpIo() = default;
1511
#endif
1512
1513
#ifdef EXV_USE_CURL
1514
//! Internal Pimpl structure of class RemoteIo.
1515
class CurlIo::CurlImpl : public Impl {
1516
 public:
1517
  //! Constructor
1518
  CurlImpl(const std::string& url, size_t blockSize);
1519
1520
  std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl_;  //!< libcurl pointer
1521
1522
  // METHODS
1523
  /*!
1524
    @brief Get the length (in bytes) of the remote file.
1525
    @return Return -1 if the size is unknown. Otherwise it returns the length of remote file (in bytes).
1526
    @throw Error if the server returns the error code.
1527
   */
1528
  [[nodiscard]] int64_t getFileLength() const override;
1529
  /*!
1530
    @brief Get the data by range.
1531
    @param startBlock The start block index.
1532
    @param stopBlock The stop block index.
1533
    @param response The data from the server.
1534
    @throw Error if the server returns the error code.
1535
    @note Set startBlock = -1 and stopBlock = -1 to get the whole file content.
1536
   */
1537
  void getDataByRange(size_t startBlock, size_t stopBlock, std::string& response) const override;
1538
  /*!
1539
    @brief Submit the data to the remote machine. The data replace a part of the remote file.
1540
          The replaced part of remote file is indicated by from and to parameters.
1541
    @param data The data are submitted to the remote machine.
1542
    @param size The size of data.
1543
    @param from The start position in the remote file where the data replace.
1544
    @param to The end position in the remote file where the data replace.
1545
    @throw Error if it fails.
1546
    @note The write access is only available on HTTP & HTTPS protocols. The data are submitted to server
1547
          via POST method. It requires the script file on the remote machine to receive the data
1548
          and edit the remote file. The server-side script may be specified with the environment
1549
          string EXIV2_HTTP_POST. The default value is "/exiv2.php". More info is available at
1550
          http://dev.exiv2.org/wiki/exiv2
1551
   */
1552
  void writeRemote(const byte* data, size_t size, size_t from, size_t to) override;
1553
1554
 private:
1555
  long timeout_;  //!< The number of seconds to wait while trying to connect.
1556
};
1557
1558
CurlIo::CurlImpl::CurlImpl(const std::string& url, size_t blockSize) :
1559
    Impl(url, blockSize), curl_(curl_easy_init(), curl_easy_cleanup) {
1560
  // The default block size for FTP is much larger than other protocols
1561
  // the reason is that getDataByRange() in FTP always creates the new connection,
1562
  // so we need the large block size to reduce the overhead of creating the connection.
1563
  if (blockSize_ == 0) {
1564
    blockSize_ = protocol_ == pFtp ? 102400 : 1024;
1565
  }
1566
1567
  std::string timeout = getEnv(envTIMEOUT);
1568
  timeout_ = std::stol(timeout);
1569
  if (timeout_ == 0) {
1570
    throw Error(ErrorCode::kerErrorMessage, "Timeout Environmental Variable must be a positive integer.");
1571
  }
1572
}
1573
1574
int64_t CurlIo::CurlImpl::getFileLength() const {
1575
  curl_easy_reset(curl_.get());  // reset all options
1576
  curl_easy_setopt(curl_.get(), CURLOPT_URL, path_.c_str());
1577
  curl_easy_setopt(curl_.get(), CURLOPT_NOBODY, 1);  // HEAD
1578
  curl_easy_setopt(curl_.get(), CURLOPT_WRITEFUNCTION, curlWriter);
1579
  curl_easy_setopt(curl_.get(), CURLOPT_SSL_VERIFYPEER, 0L);
1580
  curl_easy_setopt(curl_.get(), CURLOPT_SSL_VERIFYHOST, 0L);
1581
  curl_easy_setopt(curl_.get(), CURLOPT_CONNECTTIMEOUT, timeout_);
1582
  // curl_easy_setopt(curl_.get(), CURLOPT_VERBOSE, 1); // debugging mode
1583
1584
  /* Perform the request, res will get the return code */
1585
  if (auto res = curl_easy_perform(curl_.get()); res != CURLE_OK) {  // error happened
1586
    throw Error(ErrorCode::kerErrorMessage, curl_easy_strerror(res));
1587
  }
1588
  // get status
1589
  int serverCode;
1590
  curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &serverCode);  // get code
1591
  if (serverCode >= 400 || serverCode < 0) {
1592
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, path_);
1593
  }
1594
  // get length
1595
  curl_off_t temp;
1596
  curl_easy_getinfo(curl_.get(), CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &temp);  // return -1 if unknown
1597
  return temp;
1598
}
1599
1600
void CurlIo::CurlImpl::getDataByRange(size_t startBlock, size_t stopBlock, std::string& response) const {
1601
  curl_easy_reset(curl_.get());  // reset all options
1602
  curl_easy_setopt(curl_.get(), CURLOPT_URL, path_.c_str());
1603
  curl_easy_setopt(curl_.get(), CURLOPT_NOPROGRESS, 1L);  // no progress meter please
1604
  curl_easy_setopt(curl_.get(), CURLOPT_WRITEFUNCTION, curlWriter);
1605
  curl_easy_setopt(curl_.get(), CURLOPT_WRITEDATA, &response);
1606
  curl_easy_setopt(curl_.get(), CURLOPT_SSL_VERIFYPEER, 0L);
1607
  curl_easy_setopt(curl_.get(), CURLOPT_CONNECTTIMEOUT, timeout_);
1608
  curl_easy_setopt(curl_.get(), CURLOPT_SSL_VERIFYHOST, 0L);
1609
1610
  // curl_easy_setopt(curl_.get(), CURLOPT_VERBOSE, 1); // debugging mode
1611
1612
  if (startBlock != std::numeric_limits<size_t>::max() && stopBlock != std::numeric_limits<size_t>::max()) {
1613
    auto range = stringFormat("{}-{}", startBlock * blockSize_, (stopBlock * blockSize_) - 1);
1614
    curl_easy_setopt(curl_.get(), CURLOPT_RANGE, range.c_str());
1615
  }
1616
1617
  /* Perform the request, res will get the return code */
1618
  if (auto res = curl_easy_perform(curl_.get()); res != CURLE_OK) {
1619
    throw Error(ErrorCode::kerErrorMessage, curl_easy_strerror(res));
1620
  }
1621
  int serverCode;
1622
  curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &serverCode);  // get code
1623
  if (serverCode >= 400 || serverCode < 0) {
1624
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, path_);
1625
  }
1626
}
1627
1628
void CurlIo::CurlImpl::writeRemote(const byte* data, size_t size, size_t from, size_t to) {
1629
  std::string scriptPath(getEnv(envHTTPPOST));
1630
  if (scriptPath.empty()) {
1631
    throw Error(ErrorCode::kerErrorMessage,
1632
                "Please set the path of the server script to handle http post data to EXIV2_HTTP_POST "
1633
                "environmental variable.");
1634
  }
1635
1636
  Exiv2::Uri hostInfo = Exiv2::Uri::Parse(path_);
1637
1638
  // add the protocol and host to the path
1639
  if (scriptPath.find("://") == std::string::npos) {
1640
    if (scriptPath.front() != '/')
1641
      scriptPath = "/" + scriptPath;
1642
    scriptPath = hostInfo.Protocol + "://" + hostInfo.Host + scriptPath;
1643
  }
1644
1645
  curl_easy_reset(curl_.get());                           // reset all options
1646
  curl_easy_setopt(curl_.get(), CURLOPT_NOPROGRESS, 1L);  // no progress meter please
1647
  // curl_easy_setopt(curl_.get(), CURLOPT_VERBOSE, 1); // debugging mode
1648
  curl_easy_setopt(curl_.get(), CURLOPT_URL, scriptPath.c_str());
1649
  curl_easy_setopt(curl_.get(), CURLOPT_SSL_VERIFYPEER, 0L);
1650
1651
  // encode base64
1652
  size_t encodeLength = (((size + 2) / 3) * 4) + 1;
1653
  auto encodeData = std::make_unique<char[]>(encodeLength);
1654
  base64encode(data, size, encodeData.get(), encodeLength);
1655
  // url encode
1656
  const std::string urlencodeData = urlencode(encodeData.get());
1657
  auto postData = stringFormat("path={}&from={}&to={}&data={}", hostInfo.Path, from, to, urlencodeData);
1658
1659
  curl_easy_setopt(curl_.get(), CURLOPT_POSTFIELDS, postData.c_str());
1660
  // Perform the request, res will get the return code.
1661
  if (auto res = curl_easy_perform(curl_.get()); res != CURLE_OK) {
1662
    throw Error(ErrorCode::kerErrorMessage, curl_easy_strerror(res));
1663
  }
1664
  int serverCode;
1665
  curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &serverCode);
1666
  if (serverCode >= 400 || serverCode < 0) {
1667
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, path_);
1668
  }
1669
}
1670
1671
size_t CurlIo::write(const byte* data, size_t wcount) {
1672
  if (p_->protocol_ == pHttp || p_->protocol_ == pHttps) {
1673
    return RemoteIo::write(data, wcount);
1674
  }
1675
  throw Error(ErrorCode::kerErrorMessage, "does not support write for this protocol.");
1676
}
1677
1678
size_t CurlIo::write(BasicIo& src) {
1679
  if (p_->protocol_ == pHttp || p_->protocol_ == pHttps) {
1680
    return RemoteIo::write(src);
1681
  }
1682
  throw Error(ErrorCode::kerErrorMessage, "does not support write for this protocol.");
1683
}
1684
1685
CurlIo::CurlIo(const std::string& url, size_t blockSize) {
1686
  p_ = std::make_unique<CurlImpl>(url, blockSize);
1687
}
1688
1689
#endif
1690
1691
// *************************************************************************
1692
// free functions
1693
#ifdef EXV_ENABLE_FILESYSTEM
1694
0
DataBuf readFile(const std::string& path) {
1695
0
  FileIo file(path);
1696
0
  if (file.open("rb") != 0) {
1697
0
    throw Error(ErrorCode::kerFileOpenFailed, path, "rb", strError());
1698
0
  }
1699
0
  DataBuf buf(static_cast<size_t>(fs::file_size(path)));
1700
0
  if (file.read(buf.data(), buf.size()) != buf.size()) {
1701
0
    throw Error(ErrorCode::kerCallFailed, path, strError(), "FileIo::read");
1702
0
  }
1703
0
  return buf;
1704
0
}
1705
1706
0
size_t writeFile(const DataBuf& buf, const std::string& path) {
1707
0
  FileIo file(path);
1708
0
  if (file.open("wb") != 0) {
1709
0
    throw Error(ErrorCode::kerFileOpenFailed, path, "wb", strError());
1710
0
  }
1711
0
  return file.write(buf.c_data(), buf.size());
1712
0
}
1713
#endif
1714
1715
#ifdef EXV_USE_CURL
1716
size_t curlWriter(char* data, size_t size, size_t nmemb, std::string* writerData) {
1717
  if (!writerData)
1718
    return 0;
1719
  writerData->append(data, size * nmemb);
1720
  return size * nmemb;
1721
}
1722
#endif
1723
}  // namespace Exiv2