Logger System 0.1.3
High-performance C++20 thread-safe logging system with asynchronous capabilities
Loading...
Searching...
No Matches
file_utils.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
11#pragma once
12
14#include <filesystem>
15#include <string>
16#include <algorithm>
17
18namespace kcenon::logger::utils {
19
29public:
57 const std::filesystem::path& path,
58 const std::filesystem::path& allowed_base = ""
59 ) {
60 try {
61 // Check for obviously suspicious patterns
62 std::string path_str = path.string();
63
64 // Detect path traversal attempts
65 if (path_str.find("..") != std::string::npos) {
68 "Path contains '..' (path traversal attempt)"
69 );
70 }
71
72 // If allowed_base is specified, ensure path is within it
73 if (!allowed_base.empty()) {
74 // Convert both paths to canonical form (resolves symlinks)
75 std::filesystem::path canonical_path;
76 std::filesystem::path canonical_base;
77
78 // Get canonical base (must exist)
79 if (std::filesystem::exists(allowed_base)) {
80 canonical_base = std::filesystem::canonical(allowed_base);
81 } else {
82 canonical_base = std::filesystem::absolute(allowed_base);
83 }
84
85 // Get canonical path (may not exist yet for new files)
86 std::filesystem::path parent = path.parent_path();
87 if (!parent.empty() && std::filesystem::exists(parent)) {
88 canonical_path = std::filesystem::canonical(parent) / path.filename();
89 } else {
90 canonical_path = std::filesystem::absolute(path);
91 }
92
93 // Check if canonical_path starts with canonical_base
94 auto [base_end, path_end] = std::mismatch(
95 canonical_base.begin(), canonical_base.end(),
96 canonical_path.begin(), canonical_path.end()
97 );
98
99 if (base_end != canonical_base.end()) {
102 "Path is outside allowed directory: " + path_str
103 );
104 }
105 }
106
107 return common::ok(); // Valid path
108 } catch (const std::filesystem::filesystem_error& e) {
111 std::string("Path validation failed: ") + e.what()
112 );
113 }
114 }
115
138 static std::string sanitize_filename(const std::string& filename) {
139 if (filename.empty()) {
140 return "unnamed.log";
141 }
142
143 std::string result;
144 result.reserve(filename.size());
145
146 for (char c : filename) {
147 // Remove path separators
148 if (c == '/' || c == '\\') {
149 continue;
150 }
151
152 // Remove null bytes
153 if (c == '\0') {
154 continue;
155 }
156
157 // Replace control characters with underscore
158 if (c < 32 || c == 127) {
159 result += '_';
160 continue;
161 }
162
163 // Remove potentially dangerous characters
164 if (c == ':' || c == '*' || c == '?' || c == '"' ||
165 c == '<' || c == '>' || c == '|') {
166 result += '_';
167 continue;
168 }
169
170 result += c;
171 }
172
173 // Truncate to safe length (255 is common filesystem limit)
174 if (result.size() > 255) {
175 // Try to preserve extension
176 size_t ext_pos = result.find_last_of('.');
177 if (ext_pos != std::string::npos && ext_pos > 250) {
178 // Extension is reasonable, keep it
179 std::string ext = result.substr(ext_pos);
180 result = result.substr(0, 255 - ext.size()) + ext;
181 } else {
182 result = result.substr(0, 255);
183 }
184 }
185
186 // Ensure result is not empty
187 if (result.empty()) {
188 return "unnamed.log";
189 }
190
191 return result;
192 }
193
219 const std::filesystem::path& file,
220 std::filesystem::perms perms = std::filesystem::perms::owner_read |
221 std::filesystem::perms::owner_write
222 ) {
223 try {
224 if (!std::filesystem::exists(file)) {
227 "File does not exist: " + file.string()
228 );
229 }
230
231 std::filesystem::permissions(file, perms);
232 return common::ok();
233 } catch (const std::filesystem::filesystem_error& e) {
236 std::string("Failed to set file permissions: ") + e.what()
237 );
238 }
239 }
240
248 static bool is_absolute(const std::filesystem::path& path) {
249 return path.is_absolute();
250 }
251
260 static std::size_t get_file_size(const std::filesystem::path& path) {
261 std::error_code ec;
262 auto size = std::filesystem::file_size(path, ec);
263 if (ec) {
264 return 0;
265 }
266 return static_cast<std::size_t>(size);
267 }
268
276 static bool is_writable(const std::filesystem::path& path) {
277 std::error_code ec;
278
279 // If file doesn't exist, check if parent directory is writable
280 if (!std::filesystem::exists(path, ec)) {
281 auto parent = path.parent_path();
282 if (parent.empty()) {
283 parent = ".";
284 }
285 return std::filesystem::exists(parent, ec) &&
286 !ec; // Basic existence check
287 }
288
289 // File exists, check status
290 auto status = std::filesystem::status(path, ec);
291 if (ec) {
292 return false;
293 }
294
295 // Check if we have write permissions
296 auto perms = status.permissions();
297 return (perms & std::filesystem::perms::owner_write) != std::filesystem::perms::none;
298 }
299
311 static std::string generate_temp_filename(
312 const std::string& prefix = "temp",
313 const std::string& extension = ".tmp"
314 ) {
315 auto now = std::chrono::system_clock::now();
316 auto time_t = std::chrono::system_clock::to_time_t(now);
317
318 std::tm tm_buf{};
319#ifdef _WIN32
320 localtime_s(&tm_buf, &time_t);
321#else
322 localtime_r(&time_t, &tm_buf);
323#endif
324
325 // Format: prefix_YYYYMMDDHHMMSS_random.ext
326 char buffer[64];
327 std::snprintf(buffer, sizeof(buffer),
328 "%s_%04d%02d%02d%02d%02d%02d_%d%s",
329 sanitize_filename(prefix).c_str(),
330 tm_buf.tm_year + 1900,
331 tm_buf.tm_mon + 1,
332 tm_buf.tm_mday,
333 tm_buf.tm_hour,
334 tm_buf.tm_min,
335 tm_buf.tm_sec,
336 static_cast<int>(std::chrono::steady_clock::now().time_since_epoch().count() % 10000),
337 extension.c_str());
338
339 return std::string(buffer);
340 }
341};
342
343} // namespace kcenon::logger::utils
File utility functions for path validation and sanitization.
Definition file_utils.h:28
static std::size_t get_file_size(const std::filesystem::path &path)
Get file size.
Definition file_utils.h:260
static std::string sanitize_filename(const std::string &filename)
Sanitize filename by removing/replacing dangerous characters.
Definition file_utils.h:138
static bool is_writable(const std::filesystem::path &path)
Check if file exists and is writable.
Definition file_utils.h:276
static bool is_absolute(const std::filesystem::path &path)
Check if path is absolute.
Definition file_utils.h:248
static common::VoidResult validate_log_path(const std::filesystem::path &path, const std::filesystem::path &allowed_base="")
Validate log file path against path traversal attacks.
Definition file_utils.h:56
static std::string generate_temp_filename(const std::string &prefix="temp", const std::string &extension=".tmp")
Generate safe temporary filename.
Definition file_utils.h:311
static common::VoidResult set_file_permissions(const std::filesystem::path &file, std::filesystem::perms perms=std::filesystem::perms::owner_read|std::filesystem::perms::owner_write)
Set file permissions (Unix/POSIX systems)
Definition file_utils.h:218
Error codes specific to the logger system.
VoidResult ok()
common::VoidResult make_logger_void_result(logger_error_code code, const std::string &message="")
@ size
Rotate based on file size only.