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