Coverage Report

Created: 2026-01-17 06:14

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