Coverage Report

Created: 2026-10-28 07:09

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
25.9k
#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
83.3k
BasicIo::~BasicIo() = default;
61
62
1.69M
void BasicIo::readOrThrow(byte* buf, size_t rcount, ErrorCode err) {
63
1.69M
  const size_t nread = read(buf, rcount);
64
1.69M
  Internal::enforce(nread == rcount, err);
65
1.69M
  Internal::enforce(!error(), err);
66
1.69M
}
67
68
5.47k
void BasicIo::seekOrThrow(int64_t offset, Position pos, ErrorCode err) {
69
5.47k
  const int r = seek(offset, pos);
70
5.47k
  Internal::enforce(r == 0, err);
71
5.47k
}
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
74.6k
  ~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
74.6k
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
74.6k
}
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
13.6M
int FileIo::Impl::switchMode(OpMode opMode) {
141
13.6M
  if (opMode_ == opMode)
142
11.7M
    return 0;
143
1.96M
  OpMode oldOpMode = opMode_;
144
1.96M
  opMode_ = opMode;
145
146
1.96M
  bool reopen = true;
147
1.96M
  switch (opMode) {
148
1.00M
    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.00M
      if (openMode_.front() == 'r' || openMode_.at(1) == '+')
152
1.00M
        reopen = false;
153
1.00M
      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
960k
    case opSeek:
160
960k
      reopen = false;
161
960k
      break;
162
1.96M
  }
163
164
1.96M
  if (!reopen) {
165
    // Don't do anything when switching _from_ opSeek mode; we
166
    // flush when switching _to_ opSeek.
167
1.96M
    if (oldOpMode == opSeek)
168
1.00M
      return 0;
169
170
    // Flush. On msvcrt fflush does not do the job
171
960k
    std::fseek(fp_, 0, SEEK_CUR);
172
960k
    return 0;
173
1.96M
  }
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
300k
int FileIo::Impl::stat(StructStat& buf) const {
200
#ifdef _WIN32
201
  const auto& file = wpath_;
202
#else
203
300k
  const auto& file = path_;
204
300k
#endif
205
300k
  try {
206
300k
    buf.st_size = fs::file_size(file);
207
300k
    buf.st_mode = fs::status(file).permissions();
208
300k
    return 0;
209
300k
  } catch (const fs::filesystem_error&) {
210
0
    return -1;
211
0
  }
212
300k
}  // FileIo::Impl::stat
213
214
74.6k
FileIo::FileIo(const std::string& path) : p_(std::make_unique<Impl>(path)) {
215
74.6k
}
216
#ifdef _WIN32
217
FileIo::FileIo(const std::wstring& path) : p_(std::make_unique<Impl>(path)) {
218
}
219
#endif
220
221
74.6k
FileIo::~FileIo() {
222
74.6k
  close();
223
74.6k
}
224
225
470k
int FileIo::munmap() {
226
470k
  int rc = 0;
227
470k
  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
25.9k
    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
25.9k
  }
247
470k
  if (p_->isWriteable_) {
248
0
    if (p_->fp_)
249
0
      p_->switchMode(Impl::opRead);
250
0
    p_->isWriteable_ = false;
251
0
  }
252
470k
  p_->pMappedArea_ = nullptr;
253
470k
  p_->mappedLength_ = 0;
254
470k
  return rc;
255
470k
}
256
257
25.9k
byte* FileIo::mmap(bool isWriteable) {
258
25.9k
  if (munmap() != 0) {
259
0
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "munmap");
260
0
  }
261
25.9k
  p_->mappedLength_ = size();
262
25.9k
  p_->isWriteable_ = isWriteable;
263
25.9k
  if (p_->isWriteable_ && p_->switchMode(Impl::opWrite) != 0) {
264
0
    throw Error(ErrorCode::kerFailedToMapFileForReadWrite, path(), strError());
265
0
  }
266
25.9k
#if __has_include(<sys/mman.h>)
267
25.9k
  int prot = PROT_READ;
268
25.9k
  if (p_->isWriteable_) {
269
0
    prot |= PROT_WRITE;
270
0
  }
271
25.9k
  void* rc = ::mmap(nullptr, p_->mappedLength_, prot, MAP_SHARED, _fileno(p_->fp_), 0);
272
25.9k
  if (MAP_FAILED == rc) {
273
0
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "mmap");
274
0
  }
275
25.9k
  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
25.9k
  return p_->pMappedArea_;
329
25.9k
}
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
980k
int FileIo::seek(int64_t offset, Position pos) {
477
980k
  int fileSeek = 0;
478
980k
  switch (pos) {
479
834k
    case BasicIo::cur:
480
834k
      fileSeek = SEEK_CUR;
481
834k
      break;
482
146k
    case BasicIo::beg:
483
146k
      fileSeek = SEEK_SET;
484
146k
      break;
485
151
    case BasicIo::end:
486
151
      fileSeek = SEEK_END;
487
151
      break;
488
980k
  }
489
490
980k
  if (p_->switchMode(Impl::opSeek) != 0)
491
0
    return 1;
492
#ifdef _WIN32
493
  return _fseeki64(p_->fp_, offset, fileSeek);
494
#else
495
980k
  return fseeko(p_->fp_, offset, fileSeek);
496
980k
#endif
497
980k
}
498
499
438k
size_t FileIo::tell() const {
500
#ifdef _WIN32
501
  auto pos = _ftelli64(p_->fp_);
502
#else
503
438k
  auto pos = ftello(p_->fp_);
504
438k
#endif
505
438k
  Internal::enforce(pos >= 0, ErrorCode::kerInputDataReadFailed);
506
438k
  return static_cast<size_t>(pos);
507
438k
}
508
509
300k
size_t FileIo::size() const {
510
  // Flush and commit only if the file is open for writing
511
300k
  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
300k
  Impl::StructStat buf;
520
300k
  if (p_->stat(buf))
521
0
    return std::numeric_limits<size_t>::max();
522
300k
  return static_cast<size_t>(buf.st_size);
523
300k
}
524
525
222k
int FileIo::open() {
526
  // Default open is in read-only binary mode
527
222k
  return open("rb");
528
222k
}
529
530
222k
int FileIo::open(const std::string& mode) {
531
222k
  close();
532
222k
  p_->openMode_ = mode;
533
222k
  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
222k
  p_->fp_ = ::fopen(path().c_str(), mode.c_str());
541
222k
  if (!p_->fp_)
542
0
    return 1;
543
222k
#endif
544
222k
  return 0;
545
222k
}
546
547
147k
bool FileIo::isopen() const {
548
147k
  return p_->fp_ != nullptr;
549
147k
}
550
551
444k
int FileIo::close() {
552
444k
  int rc = 0;
553
444k
  if (munmap() != 0)
554
0
    rc = 2;
555
444k
  if (p_->fp_) {
556
222k
    if (std::fclose(p_->fp_) != 0)
557
0
      rc |= 1;
558
222k
    p_->fp_ = nullptr;
559
222k
  }
560
444k
  return rc;
561
444k
}
562
563
84.4k
DataBuf FileIo::read(size_t rcount) {
564
84.4k
  if (rcount > size())
565
0
    throw Error(ErrorCode::kerInvalidMalloc);
566
84.4k
  DataBuf buf(rcount);
567
84.4k
  size_t readCount = read(buf.data(), buf.size());
568
84.4k
  if (readCount == 0) {
569
38
    throw Error(ErrorCode::kerInputDataReadFailed);
570
38
  }
571
84.4k
  buf.resize(readCount);
572
84.4k
  return buf;
573
84.4k
}
574
575
2.78M
size_t FileIo::read(byte* buf, size_t rcount) {
576
2.78M
  if (p_->switchMode(Impl::opRead) != 0) {
577
0
    return 0;
578
0
  }
579
2.78M
  return std::fread(buf, 1, rcount, p_->fp_);
580
2.78M
}
581
582
9.91M
int FileIo::getb() {
583
9.91M
  if (p_->switchMode(Impl::opRead) != 0)
584
0
    return EOF;
585
9.91M
  return getc(p_->fp_);
586
9.91M
}
587
588
2.68M
int FileIo::error() const {
589
2.68M
  return p_->fp_ ? ferror(p_->fp_) : 0;
590
2.68M
}
591
592
1.19M
bool FileIo::eof() const {
593
1.19M
  return std::feof(p_->fp_) != 0;
594
1.19M
}
595
596
236k
const std::string& FileIo::path() const noexcept {
597
236k
  return p_->path_;
598
236k
}
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
7.55k
  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
1.07k
MemIo::Impl::Impl(const byte* data, size_t size) : data_(const_cast<byte*>(data)), size_(size) {
628
1.07k
}
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
383k
void MemIo::Impl::reserve(size_t wcount) {
682
383k
  const size_t need = wcount + idx_;
683
383k
  size_t blockSize = 32 * 1024;  // 32768
684
383k
  const size_t maxBlockSize = 4 * 1024 * 1024;
685
686
383k
  if (!isMalloced_) {
687
    // Minimum size for 1st block
688
3.73k
    auto size = std::max<size_t>(blockSize * (1 + need / blockSize), size_);
689
3.73k
    auto data = static_cast<byte*>(std::malloc(size));
690
3.73k
    if (!data) {
691
0
      throw Error(ErrorCode::kerMallocFailed);
692
0
    }
693
3.73k
    if (data_) {
694
0
      std::memcpy(data, data_, size_);
695
0
    }
696
3.73k
    data_ = data;
697
3.73k
    sizeAlloced_ = size;
698
3.73k
    isMalloced_ = true;
699
3.73k
  }
700
701
383k
  if (need > size_) {
702
380k
    if (need > sizeAlloced_) {
703
271
      blockSize = std::min(2 * sizeAlloced_, maxBlockSize);
704
      // Allocate in blocks
705
271
      size_t want = blockSize * (1 + need / blockSize);
706
271
      data_ = static_cast<byte*>(std::realloc(data_, want));
707
271
      if (!data_) {
708
0
        throw Error(ErrorCode::kerMallocFailed);
709
0
      }
710
271
      sizeAlloced_ = want;
711
271
    }
712
380k
    size_ = need;
713
380k
  }
714
383k
}
715
716
7.55k
MemIo::MemIo() : p_(std::make_unique<Impl>()) {
717
7.55k
}
718
719
1.07k
MemIo::MemIo(const byte* data, size_t size) : p_(std::make_unique<Impl>(data, size)) {
720
1.07k
}
721
722
8.63k
MemIo::~MemIo() {
723
8.63k
  if (p_->isMalloced_) {
724
3.73k
    std::free(p_->data_);
725
3.73k
  }
726
8.63k
}
727
728
380k
size_t MemIo::write(const byte* data, size_t wcount) {
729
380k
  p_->reserve(wcount);
730
380k
  if (data) {
731
378k
    std::memcpy(&p_->data_[p_->idx_], data, wcount);
732
378k
  }
733
380k
  p_->idx_ += wcount;
734
380k
  return wcount;
735
380k
}
736
737
3.50k
void MemIo::transfer(BasicIo& src) {
738
3.50k
  if (auto memIo = dynamic_cast<MemIo*>(&src)) {
739
    // Optimization if src is another instance of MemIo
740
3.50k
    if (p_->isMalloced_) {
741
0
      std::free(p_->data_);
742
0
    }
743
3.50k
    p_->idx_ = 0;
744
3.50k
    p_->data_ = memIo->p_->data_;
745
3.50k
    p_->size_ = memIo->p_->size_;
746
3.50k
    p_->isMalloced_ = memIo->p_->isMalloced_;
747
3.50k
    memIo->p_->idx_ = 0;
748
3.50k
    memIo->p_->data_ = nullptr;
749
3.50k
    memIo->p_->size_ = 0;
750
3.50k
    memIo->p_->isMalloced_ = false;
751
3.50k
  } 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
3.50k
  if (error() || src.error())
761
0
    throw Error(ErrorCode::kerMemoryTransferFailed, strError());
762
3.50k
}
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
2.27k
int MemIo::putb(byte data) {
783
2.27k
  p_->reserve(1);
784
2.27k
  p_->data_[p_->idx_++] = data;
785
2.27k
  return data;
786
2.27k
}
787
788
18.5k
int MemIo::seek(int64_t offset, Position pos) {
789
18.5k
  int64_t newIdx = 0;
790
791
18.5k
  switch (pos) {
792
16.4k
    case BasicIo::cur:
793
16.4k
      newIdx = p_->idx_ + offset;
794
16.4k
      break;
795
2.11k
    case BasicIo::beg:
796
2.11k
      newIdx = offset;
797
2.11k
      break;
798
20
    case BasicIo::end:
799
20
      newIdx = p_->size_ + offset;
800
20
      break;
801
18.5k
  }
802
803
18.5k
  if (newIdx < 0)
804
0
    return 1;
805
806
18.5k
  if (newIdx > static_cast<int64_t>(p_->size_)) {
807
75
    p_->eof_ = true;
808
75
    return 1;
809
75
  }
810
811
18.5k
  p_->idx_ = static_cast<size_t>(newIdx);
812
18.5k
  p_->eof_ = false;
813
18.5k
  return 0;
814
18.5k
}
815
816
675
byte* MemIo::mmap(bool /*isWriteable*/) {
817
675
  return p_->data_;
818
675
}
819
820
0
int MemIo::munmap() {
821
0
  return 0;
822
0
}
823
824
2.17k
size_t MemIo::tell() const {
825
2.17k
  return p_->idx_;
826
2.17k
}
827
828
5.64k
size_t MemIo::size() const {
829
5.64k
  return p_->size_;
830
5.64k
}
831
832
2.86k
int MemIo::open() {
833
2.86k
  p_->idx_ = 0;
834
2.86k
  p_->eof_ = false;
835
2.86k
  return 0;
836
2.86k
}
837
838
1.91k
bool MemIo::isopen() const {
839
1.91k
  return true;
840
1.91k
}
841
842
1.91k
int MemIo::close() {
843
1.91k
  return 0;
844
1.91k
}
845
846
5.08k
DataBuf MemIo::read(size_t rcount) {
847
5.08k
  DataBuf buf(rcount);
848
5.08k
  size_t readCount = read(buf.data(), buf.size());
849
5.08k
  buf.resize(readCount);
850
5.08k
  return buf;
851
5.08k
}
852
853
34.4k
size_t MemIo::read(byte* buf, size_t rcount) {
854
34.4k
  const auto avail = std::max<size_t>(p_->size_ - p_->idx_, 0);
855
34.4k
  const auto allow = std::min<size_t>(rcount, avail);
856
34.4k
  if (allow > 0) {
857
33.1k
    std::memcpy(buf, &p_->data_[p_->idx_], allow);
858
33.1k
  }
859
34.4k
  p_->idx_ += allow;
860
34.4k
  if (rcount > avail) {
861
697
    p_->eof_ = true;
862
697
  }
863
34.4k
  return allow;
864
34.4k
}
865
866
970k
int MemIo::getb() {
867
970k
  if (p_->idx_ >= p_->size_) {
868
52
    p_->eof_ = true;
869
52
    return EOF;
870
52
  }
871
970k
  return p_->data_[p_->idx_++];
872
970k
}
873
874
38.1k
int MemIo::error() const {
875
38.1k
  return 0;
876
38.1k
}
877
878
18.8k
bool MemIo::eof() const {
879
18.8k
  return p_->eof_;
880
18.8k
}
881
882
150
const std::string& MemIo::path() const noexcept {
883
150
  static std::string _path{"MemIo"};
884
150
  return _path;
885
150
}
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_CONNECTTIMEOUT, timeout_);
1580
  // curl_easy_setopt(curl_.get(), CURLOPT_VERBOSE, 1); // debugging mode
1581
1582
  /* Perform the request, res will get the return code */
1583
  if (auto res = curl_easy_perform(curl_.get()); res != CURLE_OK) {  // error happened
1584
    throw Error(ErrorCode::kerErrorMessage, curl_easy_strerror(res));
1585
  }
1586
  // get status
1587
  int serverCode;
1588
  curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &serverCode);  // get code
1589
  if (serverCode >= 400 || serverCode < 0) {
1590
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, path_);
1591
  }
1592
  // get length
1593
  curl_off_t temp;
1594
  curl_easy_getinfo(curl_.get(), CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &temp);  // return -1 if unknown
1595
  return temp;
1596
}
1597
1598
void CurlIo::CurlImpl::getDataByRange(size_t startBlock, size_t stopBlock, std::string& response) const {
1599
  curl_easy_reset(curl_.get());  // reset all options
1600
  curl_easy_setopt(curl_.get(), CURLOPT_URL, path_.c_str());
1601
  curl_easy_setopt(curl_.get(), CURLOPT_NOPROGRESS, 1L);  // no progress meter please
1602
  curl_easy_setopt(curl_.get(), CURLOPT_WRITEFUNCTION, curlWriter);
1603
  curl_easy_setopt(curl_.get(), CURLOPT_WRITEDATA, &response);
1604
  curl_easy_setopt(curl_.get(), CURLOPT_CONNECTTIMEOUT, timeout_);
1605
1606
  // curl_easy_setopt(curl_.get(), CURLOPT_VERBOSE, 1); // debugging mode
1607
1608
  if (startBlock != std::numeric_limits<size_t>::max() && stopBlock != std::numeric_limits<size_t>::max()) {
1609
    auto range = stringFormat("{}-{}", startBlock * blockSize_, (stopBlock * blockSize_) - 1);
1610
    curl_easy_setopt(curl_.get(), CURLOPT_RANGE, range.c_str());
1611
  }
1612
1613
  /* Perform the request, res will get the return code */
1614
  if (auto res = curl_easy_perform(curl_.get()); res != CURLE_OK) {
1615
    throw Error(ErrorCode::kerErrorMessage, curl_easy_strerror(res));
1616
  }
1617
  int serverCode;
1618
  curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &serverCode);  // get code
1619
  if (serverCode >= 400 || serverCode < 0) {
1620
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, path_);
1621
  }
1622
}
1623
1624
void CurlIo::CurlImpl::writeRemote(const byte* data, size_t size, size_t from, size_t to) {
1625
  std::string scriptPath(getEnv(envHTTPPOST));
1626
  if (scriptPath.empty()) {
1627
    throw Error(ErrorCode::kerErrorMessage,
1628
                "Please set the path of the server script to handle http post data to EXIV2_HTTP_POST "
1629
                "environmental variable.");
1630
  }
1631
1632
  Exiv2::Uri hostInfo = Exiv2::Uri::Parse(path_);
1633
1634
  // add the protocol and host to the path
1635
  if (scriptPath.find("://") == std::string::npos) {
1636
    if (scriptPath.front() != '/')
1637
      scriptPath = "/" + scriptPath;
1638
    scriptPath = hostInfo.Protocol + "://" + hostInfo.Host + scriptPath;
1639
  }
1640
1641
  curl_easy_reset(curl_.get());                           // reset all options
1642
  curl_easy_setopt(curl_.get(), CURLOPT_NOPROGRESS, 1L);  // no progress meter please
1643
  // curl_easy_setopt(curl_.get(), CURLOPT_VERBOSE, 1); // debugging mode
1644
  curl_easy_setopt(curl_.get(), CURLOPT_URL, scriptPath.c_str());
1645
1646
  // encode base64
1647
  size_t encodeLength = (((size + 2) / 3) * 4) + 1;
1648
  auto encodeData = std::make_unique<char[]>(encodeLength);
1649
  base64encode(data, size, encodeData.get(), encodeLength);
1650
  // url encode
1651
  const std::string urlencodeData = urlencode(encodeData.get());
1652
  auto postData = stringFormat("path={}&from={}&to={}&data={}", hostInfo.Path, from, to, urlencodeData);
1653
1654
  curl_easy_setopt(curl_.get(), CURLOPT_POSTFIELDS, postData.c_str());
1655
  // Perform the request, res will get the return code.
1656
  if (auto res = curl_easy_perform(curl_.get()); res != CURLE_OK) {
1657
    throw Error(ErrorCode::kerErrorMessage, curl_easy_strerror(res));
1658
  }
1659
  int serverCode;
1660
  curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &serverCode);
1661
  if (serverCode >= 400 || serverCode < 0) {
1662
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, path_);
1663
  }
1664
}
1665
1666
size_t CurlIo::write(const byte* data, size_t wcount) {
1667
  if (p_->protocol_ == pHttp || p_->protocol_ == pHttps) {
1668
    return RemoteIo::write(data, wcount);
1669
  }
1670
  throw Error(ErrorCode::kerErrorMessage, "does not support write for this protocol.");
1671
}
1672
1673
size_t CurlIo::write(BasicIo& src) {
1674
  if (p_->protocol_ == pHttp || p_->protocol_ == pHttps) {
1675
    return RemoteIo::write(src);
1676
  }
1677
  throw Error(ErrorCode::kerErrorMessage, "does not support write for this protocol.");
1678
}
1679
1680
CurlIo::CurlIo(const std::string& url, size_t blockSize) {
1681
  p_ = std::make_unique<CurlImpl>(url, blockSize);
1682
}
1683
1684
#endif
1685
1686
// *************************************************************************
1687
// free functions
1688
#ifdef EXV_ENABLE_FILESYSTEM
1689
0
DataBuf readFile(const std::string& path) {
1690
0
  FileIo file(path);
1691
0
  if (file.open("rb") != 0) {
1692
0
    throw Error(ErrorCode::kerFileOpenFailed, path, "rb", strError());
1693
0
  }
1694
0
  DataBuf buf(static_cast<size_t>(fs::file_size(path)));
1695
0
  if (file.read(buf.data(), buf.size()) != buf.size()) {
1696
0
    throw Error(ErrorCode::kerCallFailed, path, strError(), "FileIo::read");
1697
0
  }
1698
0
  return buf;
1699
0
}
1700
1701
0
size_t writeFile(const DataBuf& buf, const std::string& path) {
1702
0
  FileIo file(path);
1703
0
  if (file.open("wb") != 0) {
1704
0
    throw Error(ErrorCode::kerFileOpenFailed, path, "wb", strError());
1705
0
  }
1706
0
  return file.write(buf.c_data(), buf.size());
1707
0
}
1708
#endif
1709
1710
#ifdef EXV_USE_CURL
1711
size_t curlWriter(char* data, size_t size, size_t nmemb, std::string* writerData) {
1712
  if (!writerData)
1713
    return 0;
1714
  writerData->append(data, size * nmemb);
1715
  return size * nmemb;
1716
}
1717
#endif
1718
}  // namespace Exiv2