File: test_utils.h

package info (click to toggle)
scitokens-cpp 1.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,208 kB
  • sloc: cpp: 12,219; ansic: 596; sh: 161; python: 132; makefile: 22
file content (130 lines) | stat: -rw-r--r-- 4,181 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#ifndef SCITOKENS_TEST_UTILS_H
#define SCITOKENS_TEST_UTILS_H

#include <climits>
#include <cstdlib>
#include <string>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>

namespace scitokens_test {

/**
 * Helper class to create and manage secure temporary directories.
 * Uses mkdtemp for security and cleans up on destruction.
 *
 * Example usage:
 *   SecureTempDir temp_dir("my_test_");
 *   ASSERT_TRUE(temp_dir.valid());
 *   std::string cache_path = temp_dir.path() + "/cache";
 *   // ... use the directory ...
 *   // Directory is automatically cleaned up when temp_dir goes out of scope
 */
class SecureTempDir {
  public:
    /**
     * Create a temp directory under the specified base path.
     * @param prefix Prefix for the directory name (default: "scitokens_test_")
     * @param base_path Base path for the temp directory. If empty, uses
     *                  BINARY_DIR/tests (from CMake) or falls back to cwd/tests
     */
    explicit SecureTempDir(const std::string &prefix = "scitokens_test_",
                           const std::string &base_path = "") {
        std::string base = base_path;
        if (base.empty()) {
            // Try to use build/tests directory (set by CMake)
            const char *binary_dir = std::getenv("BINARY_DIR");
            if (binary_dir) {
                base = std::string(binary_dir) + "/tests";
            } else {
                // Fallback: use current working directory + tests
                char *cwd = getcwd(nullptr, 0);
                if (cwd) {
                    base = std::string(cwd) + "/tests";
                    free(cwd);
                } else {
                    base = "/tmp"; // Last resort fallback
                }
            }
        }

        // Ensure base directory exists
        mkdir(base.c_str(), 0700);

        // Create template for mkdtemp
        std::string tmpl = base + "/" + prefix + "XXXXXX";
        std::vector<char> tmpl_buf(tmpl.begin(), tmpl.end());
        tmpl_buf.push_back('\0');

        char *result = mkdtemp(tmpl_buf.data());
        if (result) {
            path_ = result;
        }
    }

    ~SecureTempDir() { cleanup(); }

    // Delete copy constructor and assignment
    SecureTempDir(const SecureTempDir &) = delete;
    SecureTempDir &operator=(const SecureTempDir &) = delete;

    // Allow move
    SecureTempDir(SecureTempDir &&other) noexcept
        : path_(std::move(other.path_)) {
        other.path_.clear();
    }

    SecureTempDir &operator=(SecureTempDir &&other) noexcept {
        if (this != &other) {
            cleanup();
            path_ = std::move(other.path_);
            other.path_.clear();
        }
        return *this;
    }

    /** Get the path to the temporary directory */
    const std::string &path() const { return path_; }

    /** Check if the directory was created successfully */
    bool valid() const { return !path_.empty(); }

    /** Manually trigger cleanup (also called by destructor) */
    void cleanup() {
        if (!path_.empty()) {
            remove_directory_recursive(path_);
            path_.clear();
        }
    }

  private:
    std::string path_;

    /**
     * Safely remove a directory recursively using fork/execv.
     * This prevents shell injection attacks that could occur with system().
     */
    static void remove_directory_recursive(const std::string &path) {
        pid_t pid = fork();
        if (pid == 0) {
            // Child process: exec rm -rf with path as direct argument
            // Using execv prevents any shell interpretation of the path
            char *const args[] = {const_cast<char *>("rm"),
                                  const_cast<char *>("-rf"),
                                  const_cast<char *>(path.c_str()), nullptr};
            execv("/bin/rm", args);
            _exit(1); // execv failed
        } else if (pid > 0) {
            // Parent: wait for child to complete
            int status;
            waitpid(pid, &status, 0);
        }
        // If fork failed, silently ignore (cleanup is best-effort)
    }
};

} // namespace scitokens_test

#endif // SCITOKENS_TEST_UTILS_H