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 BSSL_NAMESPACE_BEGIN 35 36 struct FileDeleter { operatorFileDeleter37 void operator()(FILE *f) const { 38 if (f != nullptr) { 39 fclose(f); 40 } 41 } 42 }; 43 44 using ScopedFILE = std::unique_ptr<FILE, FileDeleter>; 45 46 class ScopedFD { 47 public: 48 ScopedFD() = default; ScopedFD(int fd)49 explicit ScopedFD(int fd) : fd_(fd) {} ~ScopedFD()50 ~ScopedFD() { reset(); } 51 ScopedFD(ScopedFD && other)52 ScopedFD(ScopedFD &&other) { *this = std::move(other); } 53 ScopedFD &operator=(ScopedFD other) { 54 reset(other.release()); 55 return *this; 56 } 57 is_valid()58 bool is_valid() const { return fd_ >= 0; } get()59 int get() const { return fd_; } 60 release()61 int release() { return std::exchange(fd_, -1); } 62 void reset(int fd = -1) { 63 if (is_valid()) { 64 #if defined(OPENSSL_WINDOWS) 65 _close(fd_); 66 #else 67 close(fd_); 68 #endif 69 } 70 fd_ = fd; 71 } 72 73 private: 74 int fd_ = -1; 75 }; 76 77 // SkipTempFileTests returns true and prints a warning if tests involving 78 // temporary files should be skipped because of platform issues. 79 bool SkipTempFileTests(); 80 81 // TemporaryFile manages a temporary file for testing. 82 class TemporaryFile { 83 public: 84 TemporaryFile() = default; 85 ~TemporaryFile(); 86 TemporaryFile(TemporaryFile & other)87 TemporaryFile(TemporaryFile &other) { *this = std::move(other); } 88 TemporaryFile& operator=(TemporaryFile&&other) { 89 // Ensure |path_| is empty so it doesn't try to delete the File. 90 path_ = std::exchange(other.path_, {}); 91 return *this; 92 } 93 94 // Init initializes the temporary file with the specified content. It returns 95 // true on success and false on error. On error, callers should call 96 // |IgnoreTempFileErrors| to determine whether to ignore the error. 97 bool Init(bssl::Span<const uint8_t> content = {}); Init(const std::string & content)98 bool Init(const std::string &content) { 99 return Init(bssl::MakeConstSpan( 100 reinterpret_cast<const uint8_t *>(content.data()), content.size())); 101 } 102 103 // Open opens the file as a |FILE| with the specified mode. 104 ScopedFILE Open(const char *mode) const; 105 106 // Open opens the file as a file descriptor with the specified flags. 107 ScopedFD OpenFD(int flags) const; 108 109 // path returns the path to the temporary file. path()110 const std::string &path() const { return path_; } 111 112 private: 113 std::string path_; 114 }; 115 116 // TemporaryDirectory manages a temporary directory for testing. 117 class TemporaryDirectory { 118 public: 119 TemporaryDirectory() = default; 120 ~TemporaryDirectory(); 121 TemporaryDirectory(TemporaryDirectory & other)122 TemporaryDirectory(TemporaryDirectory &other) { *this = std::move(other); } 123 TemporaryDirectory& operator=(TemporaryDirectory&&other) { 124 // Ensure |other_| is empty so it doesn't try to delete the directory. 125 path_ = std::exchange(other.path_, {}); 126 files_ = std::exchange(other.files_, {}); 127 return *this; 128 } 129 130 // Init initializes the temporary directory. It returns true on success and 131 // false on error. On error, callers should call |IgnoreTempFileErrors| to 132 // determine whether to ignore the error. 133 bool Init(); 134 135 // path returns the path to the temporary directory. path()136 const std::string &path() const { return path_; } 137 138 // AddFile adds a file to the temporary directory with the specified content. 139 // It returns true on success and false on error. Subdirectories in the 140 // temporary directory are not currently supported. 141 bool AddFile(const std::string &filename, bssl::Span<const uint8_t> content); AddFile(const std::string & filename,const std::string & content)142 bool AddFile(const std::string &filename, const std::string &content) { 143 return AddFile( 144 filename, 145 bssl::MakeConstSpan(reinterpret_cast<const uint8_t *>(content.data()), 146 content.size())); 147 } 148 149 // GetFilePath returns the path to the speciifed file within the temporary 150 // directory. GetFilePath(const std::string & filename)151 std::string GetFilePath(const std::string &filename) { 152 #if defined(OPENSSL_WINDOWS) 153 return path_ + '\\' + filename; 154 #else 155 return path_ + '/' + filename; 156 #endif 157 } 158 159 private: 160 std::string path_; 161 std::set<std::string> files_; 162 }; 163 164 BSSL_NAMESPACE_END 165 166 #endif // OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H 167