Coverage Report

Created: 2026-08-11 07:29

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