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