1 // Copyright 2023 The BoringSSL Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
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 <string_view>
24 #include <utility>
25 
26 #include <openssl/span.h>
27 
28 #if defined(OPENSSL_WINDOWS)
29 #include <io.h>
30 #else
31 #include <unistd.h>
32 #endif
33 
34 
35 BSSL_NAMESPACE_BEGIN
36 
37 struct FileDeleter {
operatorFileDeleter38   void operator()(FILE *f) const {
39     if (f != nullptr) {
40       fclose(f);
41     }
42   }
43 };
44 
45 using ScopedFILE = std::unique_ptr<FILE, FileDeleter>;
46 
47 class ScopedFD {
48  public:
49   ScopedFD() = default;
ScopedFD(int fd)50   explicit ScopedFD(int fd) : fd_(fd) {}
~ScopedFD()51   ~ScopedFD() { reset(); }
52 
ScopedFD(ScopedFD && other)53   ScopedFD(ScopedFD &&other) { *this = std::move(other); }
54   ScopedFD &operator=(ScopedFD other) {
55     reset(other.release());
56     return *this;
57   }
58 
is_valid()59   bool is_valid() const { return fd_ >= 0; }
get()60   int get() const { return fd_; }
61 
release()62   int release() { return std::exchange(fd_, -1); }
63   void reset(int fd = -1) {
64     if (is_valid()) {
65 #if defined(OPENSSL_WINDOWS)
66       _close(fd_);
67 #else
68       close(fd_);
69 #endif
70     }
71     fd_ = fd;
72   }
73 
74  private:
75   int fd_ = -1;
76 };
77 
78 // SkipTempFileTests returns true and prints a warning if tests involving
79 // temporary files should be skipped because of platform issues.
80 bool SkipTempFileTests();
81 
82 // TemporaryFile manages a temporary file for testing.
83 class TemporaryFile {
84  public:
85   TemporaryFile() = default;
86   ~TemporaryFile();
87 
TemporaryFile(TemporaryFile & other)88   TemporaryFile(TemporaryFile &other) { *this = std::move(other); }
89   TemporaryFile& operator=(TemporaryFile&&other) {
90     // Ensure |path_| is empty so it doesn't try to delete the File.
91     path_ = std::exchange(other.path_, {});
92     return *this;
93   }
94 
95   // Init initializes the temporary file with the specified content. It returns
96   // true on success and false on error. On error, callers should call
97   // |IgnoreTempFileErrors| to determine whether to ignore the error.
98   bool Init(bssl::Span<const uint8_t> content = {});
Init(std::string_view content)99   bool Init(std::string_view content) {
100     return Init(bssl::StringAsBytes(content));
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,std::string_view content)142   bool AddFile(const std::string &filename, std::string_view content) {
143     return AddFile(filename, bssl::StringAsBytes(content));
144   }
145 
146   // GetFilePath returns the path to the specified file within the temporary
147   // directory.
GetFilePath(const std::string & filename)148   std::string GetFilePath(const std::string &filename) {
149 #if defined(OPENSSL_WINDOWS)
150     return path_ + '\\' + filename;
151 #else
152     return path_ + '/' + filename;
153 #endif
154   }
155 
156  private:
157   std::string path_;
158   std::set<std::string> files_;
159 };
160 
161 BSSL_NAMESPACE_END
162 
163 #endif  // OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H
164