Coverage Report

Created: 2026-02-26 07:48

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
9.88k
#define _fileno fileno
48
0
#define _isatty isatty
49
#endif
50
51
namespace Exiv2 {
52
53
44.7k
BasicIo::~BasicIo() = default;
54
55
799k
void BasicIo::readOrThrow(byte* buf, size_t rcount, ErrorCode err) {
56
799k
  const size_t nread = read(buf, rcount);
57
799k
  Internal::enforce(nread == rcount, err);
58
799k
  Internal::enforce(!error(), err);
59
799k
}
60
61
3.64k
void BasicIo::seekOrThrow(int64_t offset, Position pos, ErrorCode err) {
62
3.64k
  const int r = seek(offset, pos);
63
3.64k
  Internal::enforce(r == 0, err);
64
3.64k
}
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
38.8k
  ~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
38.8k
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
38.8k
}
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
12.6M
int FileIo::Impl::switchMode(OpMode opMode) {
134
12.6M
  if (opMode_ == opMode)
135
11.5M
    return 0;
136
1.06M
  OpMode oldOpMode = opMode_;
137
1.06M
  opMode_ = opMode;
138
139
1.06M
  bool reopen = true;
140
1.06M
  switch (opMode) {
141
544k
    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
544k
      if (openMode_.front() == 'r' || openMode_.at(1) == '+')
145
544k
        reopen = false;
146
544k
      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
516k
    case opSeek:
153
516k
      reopen = false;
154
516k
      break;
155
1.06M
  }
156
157
1.06M
  if (!reopen) {
158
    // Don't do anything when switching _from_ opSeek mode; we
159
    // flush when switching _to_ opSeek.
160
1.06M
    if (oldOpMode == opSeek)
161
544k
      return 0;
162
163
    // Flush. On msvcrt fflush does not do the job
164
516k
    std::fseek(fp_, 0, SEEK_CUR);
165
516k
    return 0;
166
1.06M
  }
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
115k
int FileIo::Impl::stat(StructStat& buf) const {
193
#ifdef _WIN32
194
  const auto& file = wpath_;
195
#else
196
115k
  const auto& file = path_;
197
115k
#endif
198
115k
  try {
199
115k
    buf.st_size = fs::file_size(file);
200
115k
    buf.st_mode = fs::status(file).permissions();
201
115k
    return 0;
202
115k
  } catch (const fs::filesystem_error&) {
203
0
    return -1;
204
0
  }
205
115k
}  // FileIo::Impl::stat
206
207
38.8k
FileIo::FileIo(const std::string& path) : p_(std::make_unique<Impl>(path)) {
208
38.8k
}
209
#ifdef _WIN32
210
FileIo::FileIo(const std::wstring& path) : p_(std::make_unique<Impl>(path)) {
211
}
212
#endif
213
214
38.8k
FileIo::~FileIo() {
215
38.8k
  close();
216
38.8k
}
217
218
239k
int FileIo::munmap() {
219
239k
  int rc = 0;
220
239k
  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
9.88k
    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
9.88k
  }
240
239k
  if (p_->isWriteable_) {
241
0
    if (p_->fp_)
242
0
      p_->switchMode(Impl::opRead);
243
0
    p_->isWriteable_ = false;
244
0
  }
245
239k
  p_->pMappedArea_ = nullptr;
246
239k
  p_->mappedLength_ = 0;
247
239k
  return rc;
248
239k
}
249
250
9.88k
byte* FileIo::mmap(bool isWriteable) {
251
9.88k
  if (munmap() != 0) {
252
0
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "munmap");
253
0
  }
254
9.88k
  p_->mappedLength_ = size();
255
9.88k
  p_->isWriteable_ = isWriteable;
256
9.88k
  if (p_->isWriteable_ && p_->switchMode(Impl::opWrite) != 0) {
257
0
    throw Error(ErrorCode::kerFailedToMapFileForReadWrite, path(), strError());
258
0
  }
259
9.88k
#if __has_include(<sys/mman.h>)
260
9.88k
  int prot = PROT_READ;
261
9.88k
  if (p_->isWriteable_) {
262
0
    prot |= PROT_WRITE;
263
0
  }
264
9.88k
  void* rc = ::mmap(nullptr, p_->mappedLength_, prot, MAP_SHARED, _fileno(p_->fp_), 0);
265
9.88k
  if (MAP_FAILED == rc) {
266
0
    throw Error(ErrorCode::kerCallFailed, path(), strError(), "mmap");
267
0
  }
268
9.88k
  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
9.88k
  return p_->pMappedArea_;
319
9.88k
}
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
520k
int FileIo::seek(int64_t offset, Position pos) {
467
520k
  int fileSeek = 0;
468
520k
  switch (pos) {
469
476k
    case BasicIo::cur:
470
476k
      fileSeek = SEEK_CUR;
471
476k
      break;
472
43.9k
    case BasicIo::beg:
473
43.9k
      fileSeek = SEEK_SET;
474
43.9k
      break;
475
141
    case BasicIo::end:
476
141
      fileSeek = SEEK_END;
477
141
      break;
478
520k
  }
479
480
520k
  if (p_->switchMode(Impl::opSeek) != 0)
481
0
    return 1;
482
#ifdef _WIN32
483
  return _fseeki64(p_->fp_, offset, fileSeek);
484
#else
485
520k
  return fseeko(p_->fp_, offset, fileSeek);
486
520k
#endif
487
520k
}
488
489
259k
size_t FileIo::tell() const {
490
#ifdef _WIN32
491
  auto pos = _ftelli64(p_->fp_);
492
#else
493
259k
  auto pos = ftello(p_->fp_);
494
259k
#endif
495
259k
  Internal::enforce(pos >= 0, ErrorCode::kerInputDataReadFailed);
496
259k
  return static_cast<size_t>(pos);
497
259k
}
498
499
115k
size_t FileIo::size() const {
500
  // Flush and commit only if the file is open for writing
501
115k
  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
115k
  Impl::StructStat buf;
510
115k
  if (p_->stat(buf))
511
0
    return std::numeric_limits<size_t>::max();
512
115k
  return static_cast<size_t>(buf.st_size);
513
115k
}
514
515
114k
int FileIo::open() {
516
  // Default open is in read-only binary mode
517
114k
  return open("rb");
518
114k
}
519
520
114k
int FileIo::open(const std::string& mode) {
521
114k
  close();
522
114k
  p_->openMode_ = mode;
523
114k
  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
114k
  p_->fp_ = ::fopen(path().c_str(), mode.c_str());
531
114k
  if (!p_->fp_)
532
0
    return 1;
533
114k
#endif
534
114k
  return 0;
535
114k
}
536
537
76.0k
bool FileIo::isopen() const {
538
76.0k
  return p_->fp_ != nullptr;
539
76.0k
}
540
541
229k
int FileIo::close() {
542
229k
  int rc = 0;
543
229k
  if (munmap() != 0)
544
0
    rc = 2;
545
229k
  if (p_->fp_) {
546
114k
    if (std::fclose(p_->fp_) != 0)
547
0
      rc |= 1;
548
114k
    p_->fp_ = nullptr;
549
114k
  }
550
229k
  return rc;
551
229k
}
552
553
32.8k
DataBuf FileIo::read(size_t rcount) {
554
32.8k
  if (rcount > size())
555
0
    throw Error(ErrorCode::kerInvalidMalloc);
556
32.8k
  DataBuf buf(rcount);
557
32.8k
  size_t readCount = read(buf.data(), buf.size());
558
32.8k
  if (readCount == 0) {
559
14
    throw Error(ErrorCode::kerInputDataReadFailed);
560
14
  }
561
32.8k
  buf.resize(readCount);
562
32.8k
  return buf;
563
32.8k
}
564
565
1.41M
size_t FileIo::read(byte* buf, size_t rcount) {
566
1.41M
  if (p_->switchMode(Impl::opRead) != 0) {
567
0
    return 0;
568
0
  }
569
1.41M
  return std::fread(buf, 1, rcount, p_->fp_);
570
1.41M
}
571
572
10.7M
int FileIo::getb() {
573
10.7M
  if (p_->switchMode(Impl::opRead) != 0)
574
0
    return EOF;
575
10.7M
  return getc(p_->fp_);
576
10.7M
}
577
578
1.41M
int FileIo::error() const {
579
1.41M
  return p_->fp_ ? ferror(p_->fp_) : 0;
580
1.41M
}
581
582
655k
bool FileIo::eof() const {
583
655k
  return std::feof(p_->fp_) != 0;
584
655k
}
585
586
119k
const std::string& FileIo::path() const noexcept {
587
119k
  return p_->path_;
588
119k
}
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
5.46k
  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
497
MemIo::Impl::Impl(const byte* data, size_t size) : data_(const_cast<byte*>(data)), size_(size) {
618
497
}
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
377k
void MemIo::Impl::reserve(size_t wcount) {
672
377k
  const size_t need = wcount + idx_;
673
377k
  size_t blockSize = 32 * 1024;  // 32768
674
377k
  const size_t maxBlockSize = 4 * 1024 * 1024;
675
676
377k
  if (!isMalloced_) {
677
    // Minimum size for 1st block
678
2.73k
    auto size = std::max<size_t>(blockSize * (1 + need / blockSize), size_);
679
2.73k
    auto data = static_cast<byte*>(std::malloc(size));
680
2.73k
    if (!data) {
681
0
      throw Error(ErrorCode::kerMallocFailed);
682
0
    }
683
2.73k
    if (data_) {
684
0
      std::memcpy(data, data_, size_);
685
0
    }
686
2.73k
    data_ = data;
687
2.73k
    sizeAlloced_ = size;
688
2.73k
    isMalloced_ = true;
689
2.73k
  }
690
691
377k
  if (need > size_) {
692
377k
    if (need > sizeAlloced_) {
693
283
      blockSize = std::min(2 * sizeAlloced_, maxBlockSize);
694
      // Allocate in blocks
695
283
      size_t want = blockSize * (1 + need / blockSize);
696
283
      data_ = static_cast<byte*>(std::realloc(data_, want));
697
283
      if (!data_) {
698
0
        throw Error(ErrorCode::kerMallocFailed);
699
0
      }
700
283
      sizeAlloced_ = want;
701
283
    }
702
377k
    size_ = need;
703
377k
  }
704
377k
}
705
706
5.46k
MemIo::MemIo() : p_(std::make_unique<Impl>()) {
707
5.46k
}
708
709
497
MemIo::MemIo(const byte* data, size_t size) : p_(std::make_unique<Impl>(data, size)) {
710
497
}
711
712
5.95k
MemIo::~MemIo() {
713
5.95k
  if (p_->isMalloced_) {
714
2.73k
    std::free(p_->data_);
715
2.73k
  }
716
5.95k
}
717
718
375k
size_t MemIo::write(const byte* data, size_t wcount) {
719
375k
  p_->reserve(wcount);
720
375k
  if (data) {
721
375k
    std::memcpy(&p_->data_[p_->idx_], data, wcount);
722
375k
  }
723
375k
  p_->idx_ += wcount;
724
375k
  return wcount;
725
375k
}
726
727
2.69k
void MemIo::transfer(BasicIo& src) {
728
2.69k
  if (auto memIo = dynamic_cast<MemIo*>(&src)) {
729
    // Optimization if src is another instance of MemIo
730
2.69k
    if (p_->isMalloced_) {
731
0
      std::free(p_->data_);
732
0
    }
733
2.69k
    p_->idx_ = 0;
734
2.69k
    p_->data_ = memIo->p_->data_;
735
2.69k
    p_->size_ = memIo->p_->size_;
736
2.69k
    p_->isMalloced_ = memIo->p_->isMalloced_;
737
2.69k
    memIo->p_->idx_ = 0;
738
2.69k
    memIo->p_->data_ = nullptr;
739
2.69k
    memIo->p_->size_ = 0;
740
2.69k
    memIo->p_->isMalloced_ = false;
741
2.69k
  } 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
2.69k
  if (error() || src.error())
751
0
    throw Error(ErrorCode::kerMemoryTransferFailed, strError());
752
2.69k
}
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
2.14k
int MemIo::putb(byte data) {
773
2.14k
  p_->reserve(1);
774
2.14k
  p_->data_[p_->idx_++] = data;
775
2.14k
  return data;
776
2.14k
}
777
778
8.03k
int MemIo::seek(int64_t offset, Position pos) {
779
8.03k
  int64_t newIdx = 0;
780
781
8.03k
  switch (pos) {
782
6.97k
    case BasicIo::cur:
783
6.97k
      newIdx = p_->idx_ + offset;
784
6.97k
      break;
785
1.05k
    case BasicIo::beg:
786
1.05k
      newIdx = offset;
787
1.05k
      break;
788
10
    case BasicIo::end:
789
10
      newIdx = p_->size_ + offset;
790
10
      break;
791
8.03k
  }
792
793
8.03k
  if (newIdx < 0)
794
0
    return 1;
795
796
8.03k
  if (newIdx > static_cast<int64_t>(p_->size_)) {
797
74
    p_->eof_ = true;
798
74
    return 1;
799
74
  }
800
801
7.96k
  p_->idx_ = static_cast<size_t>(newIdx);
802
7.96k
  p_->eof_ = false;
803
7.96k
  return 0;
804
8.03k
}
805
806
203
byte* MemIo::mmap(bool /*isWriteable*/) {
807
203
  return p_->data_;
808
203
}
809
810
0
int MemIo::munmap() {
811
0
  return 0;
812
0
}
813
814
2.07k
size_t MemIo::tell() const {
815
2.07k
  return p_->idx_;
816
2.07k
}
817
818
4.50k
size_t MemIo::size() const {
819
4.50k
  return p_->size_;
820
4.50k
}
821
822
1.20k
int MemIo::open() {
823
1.20k
  p_->idx_ = 0;
824
1.20k
  p_->eof_ = false;
825
1.20k
  return 0;
826
1.20k
}
827
828
810
bool MemIo::isopen() const {
829
810
  return true;
830
810
}
831
832
810
int MemIo::close() {
833
810
  return 0;
834
810
}
835
836
3.50k
DataBuf MemIo::read(size_t rcount) {
837
3.50k
  DataBuf buf(rcount);
838
3.50k
  size_t readCount = read(buf.data(), buf.size());
839
3.50k
  buf.resize(readCount);
840
3.50k
  return buf;
841
3.50k
}
842
843
15.8k
size_t MemIo::read(byte* buf, size_t rcount) {
844
15.8k
  const auto avail = std::max<size_t>(p_->size_ - p_->idx_, 0);
845
15.8k
  const auto allow = std::min<size_t>(rcount, avail);
846
15.8k
  if (allow > 0) {
847
15.1k
    std::memcpy(buf, &p_->data_[p_->idx_], allow);
848
15.1k
  }
849
15.8k
  p_->idx_ += allow;
850
15.8k
  if (rcount > avail) {
851
416
    p_->eof_ = true;
852
416
  }
853
15.8k
  return allow;
854
15.8k
}
855
856
504k
int MemIo::getb() {
857
504k
  if (p_->idx_ >= p_->size_) {
858
16
    p_->eof_ = true;
859
16
    return EOF;
860
16
  }
861
504k
  return p_->data_[p_->idx_++];
862
504k
}
863
864
17.4k
int MemIo::error() const {
865
17.4k
  return 0;
866
17.4k
}
867
868
8.46k
bool MemIo::eof() const {
869
8.46k
  return p_->eof_;
870
8.46k
}
871
872
99
const std::string& MemIo::path() const noexcept {
873
99
  static std::string _path{"MemIo"};
874
99
  return _path;
875
99
}
876
877
0
void MemIo::populateFakeData() {
878
0
}
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
    auto readBuf = std::make_unique<char[]>(100 * 1024);
934
0
    std::streamsize readBufSize = 0;
935
0
    do {
936
0
      std::cin.read(readBuf.get(), 100 * 1024);
937
0
      readBufSize = std::cin.gcount();
938
0
      if (readBufSize > 0) {
939
0
        fs.write(readBuf.get(), 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
HttpIo::HttpImpl::HttpImpl(const std::string& url, size_t blockSize) : Impl(url, blockSize) {
1384
  hostInfo_ = Exiv2::Uri::Parse(url);
1385
  Exiv2::Uri::Decode(hostInfo_);
1386
}
1387
1388
int64_t HttpIo::HttpImpl::getFileLength() const {
1389
  Exiv2::Dictionary response;
1390
  Exiv2::Dictionary request;
1391
  std::string errors;
1392
  request["server"] = hostInfo_.Host;
1393
  request["page"] = hostInfo_.Path;
1394
  if (!hostInfo_.Port.empty())
1395
    request["port"] = hostInfo_.Port;
1396
  request["verb"] = "HEAD";
1397
  int serverCode = http(request, response, errors);
1398
  if (serverCode < 0 || serverCode >= 400 || !errors.empty()) {
1399
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, hostInfo_.Path);
1400
  }
1401
1402
  auto lengthIter = response.find("Content-Length");
1403
  return (lengthIter == response.end()) ? -1 : std::stoll(lengthIter->second);
1404
}
1405
1406
void HttpIo::HttpImpl::getDataByRange(size_t lowBlock, size_t highBlock, std::string& response) const {
1407
  Exiv2::Dictionary responseDic;
1408
  Exiv2::Dictionary request;
1409
  request["server"] = hostInfo_.Host;
1410
  request["page"] = hostInfo_.Path;
1411
  if (!hostInfo_.Port.empty())
1412
    request["port"] = hostInfo_.Port;
1413
  request["verb"] = "GET";
1414
  std::string errors;
1415
  if (lowBlock != std::numeric_limits<size_t>::max() && highBlock != std::numeric_limits<size_t>::max()) {
1416
    request["header"] = stringFormat("Range: bytes={}-{}", lowBlock * blockSize_, (highBlock + 1) * (blockSize_ - 1));
1417
  }
1418
1419
  int serverCode = http(request, responseDic, errors);
1420
  if (serverCode < 0 || serverCode >= 400 || !errors.empty()) {
1421
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, hostInfo_.Path);
1422
  }
1423
  response = responseDic["body"];
1424
}
1425
1426
void HttpIo::HttpImpl::writeRemote(const byte* data, size_t size, size_t from, size_t to) {
1427
  std::string scriptPath(getEnv(envHTTPPOST));
1428
  if (scriptPath.empty()) {
1429
    throw Error(ErrorCode::kerErrorMessage,
1430
                "Please set the path of the server script to handle http post data to EXIV2_HTTP_POST "
1431
                "environmental variable.");
1432
  }
1433
1434
  // standardize the path without "/" at the beginning.
1435
  if (scriptPath.find("://") == std::string::npos && scriptPath.front() != '/') {
1436
    scriptPath = "/" + scriptPath;
1437
  }
1438
1439
  Exiv2::Dictionary response;
1440
  Exiv2::Dictionary request;
1441
  std::string errors;
1442
1443
  Uri scriptUri = Exiv2::Uri::Parse(scriptPath);
1444
  request["server"] = scriptUri.Host.empty() ? hostInfo_.Host : scriptUri.Host;
1445
  if (!scriptUri.Port.empty())
1446
    request["port"] = scriptUri.Port;
1447
  request["page"] = scriptUri.Path;
1448
  request["verb"] = "POST";
1449
1450
  // encode base64
1451
  size_t encodeLength = (((size + 2) / 3) * 4) + 1;
1452
  auto encodeData = std::make_unique<char[]>(encodeLength);
1453
  base64encode(data, size, encodeData.get(), encodeLength);
1454
  // url encode
1455
  const std::string urlencodeData = urlencode(encodeData.get());
1456
1457
  auto postData = stringFormat("path={}&from={}&to={}&data={}", hostInfo_.Path, from, to, urlencodeData);
1458
1459
  // create the header
1460
  auto header = stringFormat(
1461
      "Content-Length: {}\n"
1462
      "Content-Type: application/x-www-form-urlencoded\n"
1463
      "\n{}\r\n",
1464
      postData.length(), postData);
1465
  request["header"] = std::move(header);
1466
1467
  int serverCode = http(request, response, errors);
1468
  if (serverCode < 0 || serverCode >= 400 || !errors.empty()) {
1469
    throw Error(ErrorCode::kerFileOpenFailed, "http", serverCode, hostInfo_.Path);
1470
  }
1471
}
1472
1473
HttpIo::HttpIo(const std::string& url, size_t blockSize) {
1474
  p_ = std::make_unique<HttpImpl>(url, blockSize);
1475
}
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