File: test_utils.h

package info (click to toggle)
scitokens-cpp 1.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,172 kB
  • sloc: cpp: 11,717; ansic: 596; sh: 161; python: 132; makefile: 22
file content (129 lines) | stat: -rw-r--r-- 4,159 bytes parent folder | download | duplicates (2)
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
#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[PATH_MAX];
                if (getcwd(cwd, sizeof(cwd))) {
                    base = std::string(cwd) + "/tests";
                } 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