1 /* Copyright (c) 2023, Google Inc. 2 * 3 * Permission to use, copy, modify, and/or distribute this software for any 4 * purpose with or without fee is hereby granted, provided that the above 5 * copyright notice and this permission notice appear in all copies. 6 * 7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ 14 15 #ifndef OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H 16 #define OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H 17 18 #include <stdio.h> 19 20 #include <memory> 21 #include <set> 22 #include <string> 23 #include <utility> 24 25 #include <openssl/span.h> 26 27 #if defined(OPENSSL_WINDOWS) 28 #include <io.h> 29 #else 30 #include <unistd.h> 31 #endif 32 33 34 struct FileDeleter { operatorFileDeleter35 void operator()(FILE *f) const { 36 if (f != nullptr) { 37 fclose(f); 38 } 39 } 40 }; 41 42 using ScopedFILE = std::unique_ptr<FILE, FileDeleter>; 43 44 class ScopedFD { 45 public: 46 ScopedFD() = default; ScopedFD(int fd)47 explicit ScopedFD(int fd) : fd_(fd) {} ~ScopedFD()48 ~ScopedFD() { reset(); } 49 ScopedFD(ScopedFD && other)50 ScopedFD(ScopedFD &&other) { *this = std::move(other); } 51 ScopedFD &operator=(ScopedFD other) { 52 reset(other.release()); 53 return *this; 54 } 55 is_valid()56 bool is_valid() const { return fd_ >= 0; } get()57 int get() const { return fd_; } 58 release()59 int release() { return std::exchange(fd_, -1); } 60 void reset(int fd = -1) { 61 if (is_valid()) { 62 #if defined(OPENSSL_WINDOWS) 63 _close(fd_); 64 #else 65 close(fd_); 66 #endif 67 } 68 fd_ = fd; 69 } 70 71 private: 72 int fd_ = -1; 73 }; 74 75 // SkipTempFileTests returns true and prints a warning if tests involving 76 // temporary files should be skipped because of platform issues. 77 bool SkipTempFileTests(); 78 79 // TemporaryFile manages a temporary file for testing. 80 class TemporaryFile { 81 public: 82 TemporaryFile() = default; 83 ~TemporaryFile(); 84 TemporaryFile(TemporaryFile & other)85 TemporaryFile(TemporaryFile &other) { *this = std::move(other); } 86 TemporaryFile& operator=(TemporaryFile&&other) { 87 // Ensure |path_| is empty so it doesn't try to delete the File. 88 path_ = std::exchange(other.path_, {}); 89 return *this; 90 } 91 92 // Init initializes the temporary file with the specified content. It returns 93 // true on success and false on error. On error, callers should call 94 // |IgnoreTempFileErrors| to determine whether to ignore the error. 95 bool Init(bssl::Span<const uint8_t> content = {}); Init(const std::string & content)96 bool Init(const std::string &content) { 97 return Init(bssl::MakeConstSpan( 98 reinterpret_cast<const uint8_t *>(content.data()), content.size())); 99 } 100 101 // Open opens the file as a |FILE| with the specified mode. 102 ScopedFILE Open(const char *mode) const; 103 104 // Open opens the file as a file descriptor with the specified flags. 105 ScopedFD OpenFD(int flags) const; 106 107 // path returns the path to the temporary file. path()108 const std::string &path() const { return path_; } 109 110 private: 111 std::string path_; 112 }; 113 114 // TemporaryDirectory manages a temporary directory for testing. 115 class TemporaryDirectory { 116 public: 117 TemporaryDirectory() = default; 118 ~TemporaryDirectory(); 119 TemporaryDirectory(TemporaryDirectory & other)120 TemporaryDirectory(TemporaryDirectory &other) { *this = std::move(other); } 121 TemporaryDirectory& operator=(TemporaryDirectory&&other) { 122 // Ensure |other_| is empty so it doesn't try to delete the directory. 123 path_ = std::exchange(other.path_, {}); 124 files_ = std::exchange(other.files_, {}); 125 return *this; 126 } 127 128 // Init initializes the temporary directory. It returns true on success and 129 // false on error. On error, callers should call |IgnoreTempFileErrors| to 130 // determine whether to ignore the error. 131 bool Init(); 132 133 // path returns the path to the temporary directory. path()134 const std::string &path() const { return path_; } 135 136 // AddFile adds a file to the temporary directory with the specified content. 137 // It returns true on success and false on error. Subdirectories in the 138 // temporary directory are not currently supported. 139 bool AddFile(const std::string &filename, bssl::Span<const uint8_t> content); AddFile(const std::string & filename,const std::string & content)140 bool AddFile(const std::string &filename, const std::string &content) { 141 return AddFile( 142 filename, 143 bssl::MakeConstSpan(reinterpret_cast<const uint8_t *>(content.data()), 144 content.size())); 145 } 146 147 // GetFilePath returns the path to the speciifed file within the temporary 148 // directory. GetFilePath(const std::string & filename)149 std::string GetFilePath(const std::string &filename) { 150 #if defined(OPENSSL_WINDOWS) 151 return path_ + '\\' + filename; 152 #else 153 return path_ + '/' + filename; 154 #endif 155 } 156 157 private: 158 std::string path_; 159 std::set<std::string> files_; 160 }; 161 162 #endif // OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H 163