#include "filer_plugin.h" #include #include #include #include #include #include namespace fs = std::filesystem; class LocalFiler : public Filer { public: LocalFiler() {} file_data setRoot(std::string _root) { struct file_data fd; try { // Always convert to absolute path fs::path new_root = fs::absolute(fs::weakly_canonical(_root)); fd.path = new_root.string().c_str(); // Create directory if it doesn't exist if (!fs::exists(new_root)) { if (!fs::create_directories(new_root)) { fd.error = {FilerStatusCodes::NoPermission, "Failed to create root directory"}; return fd; } } // Set the absolute root path root = new_root; // Reset CWD to root-relative path cwd = "/"; } catch (const fs::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, "setRoot error: %s", ex.what()); fd.error = {FilerStatusCodes::Exception, ex.what()}; } return fd; } file_data setCWD(std::string _cwd) { struct file_data fd; try { // Always convert to absolute path fs::path new_cwd = fs::weakly_canonical(_cwd); fd.path = new_cwd.string().c_str(); // Create directory if it doesn't exist if (!fs::exists(root / new_cwd)) { if (!fs::create_directories(root / new_cwd)) { fd.error = {FilerStatusCodes::NoPermission, "Failed to create cwd directory"}; return fd; } } cwd = new_cwd; } catch (const fs::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, "setCWD error: %s", ex.what()); fd.error = {FilerStatusCodes::Exception, ex.what()}; } return fd; } fs::path getRoot() { return this->root; } fs::path getCWD() { return this->cwd; } fs::path resolvePath(const std::string& path) { try { if (path.empty() || path == ".") { return root / cwd.relative_path(); } fs::path target_path; if (path[0] == '/') { // Absolute path relative to FTP root target_path = root / path.substr(1); } else { // Relative to current directory target_path = root / cwd.relative_path() / path; } // Remove .. and . components target_path = fs::weakly_canonical(target_path); // Make sure we haven't escaped the root if (!target_path.string().starts_with(root.string())) { return root / cwd.relative_path(); } return target_path; } catch (const std::exception& e) { logger->print(LOGLEVEL_ERROR, "Path resolution error: %s", e.what()); return root / cwd.relative_path(); } } file_data traverse(std::string dir) { struct file_data fd; try { // Handle special cases if (dir.empty() || dir == ".") { fd.path = resolvePath(".").string().c_str(); return fd; } fs::path requested_path = resolvePath(dir); // Verify the requested path exists and is within root if (!fs::exists(requested_path)) { fd.error = file_error{FilerStatusCodes::NotFound, "Directory Not Found"}; return fd; } if (!fs::is_directory(requested_path)) { fd.error = file_error{FilerStatusCodes::NotFound, "Not a Directory"}; return fd; } // Make sure path is within root directory fs::path rel_path = fs::relative(requested_path, root); if (rel_path.string().find("..") == 0) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } // Update current working directory relative to root cwd = "/" + rel_path.string(); fd.path = requested_path.string().c_str(); } catch (const fs::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, "Path fallback: %s", ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd; } file_data createDirectory(std::string dir) { struct file_data fd; try { fs::path resolved = resolvePath(dir); fd.path = resolved.string().c_str(); if (fs::exists(resolved)) { fd.error = file_error{FilerStatusCodes::FileExists, "Directory Already Exists"}; return fd; } fs::create_directory(resolved); } catch (const std::filesystem::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd; } file_data fileSize(std::string name) { struct file_data fd; try { fs::path resolved = resolvePath(name); fd.path = resolved.string().c_str(); if (!fs::exists(resolved)) { fd.error = file_error{FilerStatusCodes::NotFound, "File Not Found"}; return fd; } if (type == 'A') { fd.error = file_error{FilerStatusCodes::InvalidTransferMode, "Refusing to transfer in ASCII mode"}; return fd; } std::ifstream infile(resolved, std::ios::in|std::ios::binary|std::ios::ate); if (infile.is_open()) { fd.size = infile.tellg(); } else { fd.error = file_error{FilerStatusCodes::NoPermission, "Unable to open file"}; } } catch (const std::filesystem::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd; } file_data deleteFile(std::string name) { struct file_data fd; try { fs::path resolved = resolvePath(name); fd.path = resolved.string().c_str(); if (!fs::exists(resolved)) { fd.error = file_error{FilerStatusCodes::NotFound, "File Not Found"}; return fd; } if (fs::is_directory(resolved) && !fs::is_empty(resolved)) { fd.error = file_error{FilerStatusCodes::DirectoryNotEmpty, "Directory not empty"}; return fd; } if (!fs::remove(resolved)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Unable to delete file"}; } } catch (const std::filesystem::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd; } file_data readFile(std::string name) { struct file_data fd; try { fs::path resolved = resolvePath(name); fd.path = resolved.string().c_str(); if (!fs::exists(resolved)) { fd.error = file_error{FilerStatusCodes::NotFound, "File Not Found"}; return fd; } if (type != 'A') { // Create a shared_ptr to manage the ifstream fd.stream = std::make_shared(resolved, std::ios::in|std::ios::binary); if (fd.stream && fd.stream->is_open()) { // Get file size fd.stream->seekg(0, std::ios::end); fd.size = fd.stream->tellg(); fd.stream->seekg(0, std::ios::beg); } else { fd.error = file_error{FilerStatusCodes::NoPermission, "Unable to open file"}; } } else { fd.error = file_error{FilerStatusCodes::InvalidTransferMode, "Refusing to transfer in ASCII mode"}; } } catch (const std::filesystem::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd; } file_data writeFile(std::string name, unsigned char* data, int size, bool append = false) { struct file_data fd; try { fs::path resolved = resolvePath(name); fd.path = resolved.string().c_str(); std::ios_base::openmode omode = std::ios::out|std::ios::binary; if (append) omode |= std::ios::app; std::ofstream outfile(resolved, omode); if (outfile.is_open()) { outfile.write((char *)data, size); outfile.close(); fd.size = size; } else { fd.error = file_error{FilerStatusCodes::NoPermission, "Unable to open file"}; } } catch (const std::filesystem::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd; } struct file_data renameFile(const std::string& from, const std::string& to) { struct file_data fd; std::filesystem::path src_path = resolvePath(from); std::filesystem::path dst_path = resolvePath(to); try { // Check if destination already exists if (std::filesystem::exists(dst_path)) { fd.error = {FilerStatusCodes::FileExists, "Destination file already exists"}; return fd; } // Perform the rename operation std::filesystem::rename(src_path, dst_path); fd.error = {0, "OK"}; } catch (const std::filesystem::filesystem_error& e) { fd.error = {FilerStatusCodes::AccessDenied, e.what()}; } return fd; } file_data list(std::string path = ".") { struct file_data fd; try { fs::path resolved = resolvePath(path); fd.path = resolved.string().c_str(); if (!fs::exists(resolved)) { fd.error = file_error{FilerStatusCodes::NotFound, "File Not Found"}; return fd; } std::ostringstream listStream; for(const auto& p : fs::directory_iterator(resolved, fs::directory_options::follow_directory_symlink | fs::directory_options::skip_permission_denied)) { struct stat fstat; struct tm *time; time_t rawtime; char timebuff[80]; if (stat(p.path().c_str(), &fstat) == -1) { fd.error = file_error{FilerStatusCodes::NoPermission, "Unable to stat file"}; break; } /* Convert time_t to tm struct */ rawtime = fstat.st_mtime; time = localtime(&rawtime); strftime(timebuff, 80, "%b %d %H:%M", time); // God should've smitten me before I wrote such attrocities. char* line; fs::perms fperms = fs::status(p).permissions(); asprintf( &line, "%c%c%c%c%c%c%c%c%c%c %4u %4u %4u %12u %s %s\r\n", p.is_directory()?'d':'-', (fperms & fs::perms::owner_read) != fs::perms::none?'r':'-', (fperms & fs::perms::owner_write) != fs::perms::none?'w':'-', (fperms & fs::perms::owner_exec) != fs::perms::none?'x':'-', (fperms & fs::perms::group_read) != fs::perms::none?'r':'-', (fperms & fs::perms::group_write) != fs::perms::none?'w':'-', (fperms & fs::perms::group_exec) != fs::perms::none?'x':'-', (fperms & fs::perms::others_read) != fs::perms::none?'r':'-', (fperms & fs::perms::others_write) != fs::perms::none?'w':'-', (fperms & fs::perms::others_exec) != fs::perms::none?'x':'-', fs::hard_link_count(p), fstat.st_uid, fstat.st_gid, fstat.st_size, timebuff, p.path().filename().c_str() ); listStream << std::string(line); free(line); } fd.size = listStream.tellp(); fd.bin = new char[fd.size]; std::strncpy(fd.bin, listStream.str().c_str(), fd.size); } catch (const std::filesystem::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd; } private: fs::path root; fs::path cwd; char type = 'I'; }; IMPLEMENT_FILER_PLUGIN(LocalFiler, "local", "Local filesystem implementation", "1.0.0")