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