#include "plugin.h" #include "filer.h" #include #include #include #include #include #include #include namespace fs = std::filesystem; class LocalFiler : public IPlugin, public Filer { public: LocalFiler() {} bool initialize(const std::map& config) { auto symreslv_it = config.find("resolve_external_symlinks"); this->resolve_ext_symlinks = (symreslv_it != config.end()) ? (symreslv_it->second == "on" || symreslv_it->second == "true" || symreslv_it->second == "yes") : false; return true; } 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 = strdup(new_root.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: {}", 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 = strdup(new_cwd.c_str()); fd.relpath = strdup(fs::relative(new_cwd, root).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: {}", ex.what()); fd.error = {FilerStatusCodes::Exception, ex.what()}; } return fd; } fs::path getRoot() { return this->root; } fs::path getCWD() { return this->cwd; } bool resolvePath(const std::string& path, fs::path* target) { return this->resolvePath(path, target, true); } bool resolvePath(const std::string& path, fs::path* target, bool resolve_symlink) { try { if (path.empty() || path == ".") { *target = root / cwd.relative_path(); return true; } if (path[0] == '/') { // Absolute path relative to FTP root *target = root / path.substr(1); } else { // Relative to current directory *target = root / cwd.relative_path() / path; } *target = fs::path(*target).lexically_normal(); if (fs::is_symlink(*target) && resolve_symlink) { fs::path resolved_sym = fs::weakly_canonical(*target); logger->print(LOGLEVEL_DEBUG, "resolved symlink path: {}", resolved_sym.string()); return resolve_ext_symlinks ? true : resolved_sym.string().starts_with(root.string()); } else if (!(*target).string().starts_with(root.string())) return false; logger->print(LOGLEVEL_DEBUG, "resolved path: {}", target->string()); return true; } catch (const std::exception& e) { logger->print(LOGLEVEL_ERROR, "path resolution error: {}", e.what()); return false; } } file_data traverse(std::string dir) { struct file_data fd; try { fs::path requested_path; if (!resolvePath(dir, &requested_path, false)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } if (dir.empty() || dir == ".") { fd.path = strdup(requested_path.c_str()); fd.relpath = strdup(fs::relative(requested_path, root).c_str()); return fd; } // 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; } // Update current working directory relative to root cwd = "/" + fs::path(requested_path).lexically_relative(root).string(); fd.path = strdup(requested_path.c_str()); fd.relpath = strdup(fs::relative(requested_path, root).c_str()); } catch (const fs::filesystem_error& ex) { logger->print(LOGLEVEL_ERROR, "traversal error: {}", 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; if (!resolvePath(dir, &resolved)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } fd.path = strdup(resolved.c_str()); fd.relpath = strdup(fs::relative(resolved, root).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; if (!resolvePath(name, &resolved)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } fd.path = strdup(resolved.c_str()); fd.relpath = strdup(fs::relative(resolved, root).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; if (!resolvePath(name, &resolved, false)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } fd.path = strdup(resolved.c_str()); fd.relpath = strdup(fs::relative(resolved, root).c_str()); std::error_code err; if (!fs::remove(resolved, err)) { if (err == std::errc::no_such_file_or_directory) fd.error = file_error{FilerStatusCodes::NotFound, "File Not Found"}; else if (err == std::errc::permission_denied) fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; else if (err == std::errc::directory_not_empty) fd.error = file_error{FilerStatusCodes::DirectoryNotEmpty, "Directory not empty"}; else if (err == std::errc::is_a_directory) fd.error = file_error{FilerStatusCodes::IsDirectory, "Is A Directory"}; else 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; if (!resolvePath(name, &resolved)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } fd.path = strdup(resolved.c_str()); fd.relpath = strdup(fs::relative(resolved, root).c_str()); if (!fs::exists(resolved)) { fd.error = file_error{FilerStatusCodes::NotFound, "File Not Found"}; return fd; } if (fs::is_directory(resolved)) { return list(std::string(fd.relpath)); } 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; if (!resolvePath(name, &resolved)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } fd.path = strdup(resolved.c_str()); fd.relpath = strdup(fs::relative(resolved, root).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; fs::path src_path; if (!resolvePath(from, &src_path)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Source Permissions"}; return fd; } fs::path dst_path; if (!resolvePath(to, &dst_path)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Destination Permissions"}; return fd; } 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.path = strdup(dst_path.c_str()); fd.relpath = strdup(fs::relative(dst_path, root).c_str()); 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; if (!resolvePath(path, &resolved)) { fd.error = file_error{FilerStatusCodes::NoPermission, "Invalid Permissions"}; return fd; } fd.path = strdup(resolved.c_str()); fd.relpath = strdup(fs::relative(resolved, root).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::skip_permission_denied)) { struct stat fstat; struct tm *time; time_t rawtime; char timebuff[80]; if (lstat(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::symlink_status(p).permissions(); // Handle symlink info along with special cases bool is_link = false; std::string link_path; if ((is_link = S_ISLNK(fstat.st_mode))) { fs::path sym_resolved; if (resolvePath(fs::read_symlink(p), &sym_resolved)) link_path = sym_resolved.lexically_relative(resolved).string(); else is_link = false; } asprintf( &line, "%c%c%c%c%c%c%c%c%c%c %4u %4u %4u %12u %s %s%s\r\n", is_link?'l':(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':'-', fstat.st_nlink, fstat.st_uid, fstat.st_gid, fstat.st_size, timebuff, p.path().filename().c_str(), is_link?(" -> " + link_path).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; bool resolve_ext_symlinks = false; char type = 'I'; }; IMPLEMENT_PLUGIN(LocalFiler, Filer, "local", "Local filesystem implementation", "1.0.0")