#include "utils.hpp" #include "httplib.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace dropshell { std::string magic_string() { return "-_-"; } bool has_magic_string(std::string name) { return name.find(magic_string()) != std::string::npos; } void maketitle(const std::string& title, sColour colour) { colourstream(colour) << std::string(title.length() + 4, '-') << std::endl; colourstream(colour) << "| " << title << " |" << std::endl; colourstream(colour) << std::string(title.length() + 4, '-') << std::endl; } bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line) { std::ifstream input_file(file_path); std::vector file_lines; std::string line; if (!input_file.is_open()) { error << "Unable to open file: " << file_path << std::endl; return false; } while (std::getline(input_file, line)) { if (line.find(search_string) != std::string::npos) file_lines.push_back(replacement_line); else file_lines.push_back(line); } input_file.close(); std::ofstream output_file(file_path); if (!output_file.is_open()) { error << "Unable to open file: " << file_path << std::endl; return false; } for (const auto& modified_line : file_lines) output_file << modified_line << "\n"; output_file.close(); return true; } std::string trim(std::string str) { // Trim leading whitespace str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) { return !std::isspace(ch); })); // Trim trailing whitespace str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), str.end()); return str; } std::string dequote(std::string str) { if (str.length() < 2) return str; if (str.front() == '"' && str.back() == '"') { return str.substr(1, str.length() - 2); } return str; } std::string quote(std::string str) { return "\""+str+"\""; } std::string halfquote(std::string str) { return "'" + str + "'"; } std::string escapequotes(std::string str) { return std::regex_replace(str, std::regex("\""), "\\\""); } std::string multi2string(std::vector values) { std::string result; for (const auto& value : values) { // remove any " contained in the string value, if present result += dequote(trim(value)) + ","; } if (!result.empty()) result.pop_back(); // Remove the last comma return result; } std::vector string2multi(std::string values) { std::vector result; values = dequote(trim(values)); // Return values separated by commas, but ignore commas within quotes bool inside_quotes = false; std::string current_item; for (char c : values) { if (c == '"') { inside_quotes = !inside_quotes; } else if (c == ',' && !inside_quotes) { if (!current_item.empty()) { std::string final = dequote(trim(current_item)); if (!final.empty()) result.push_back(final); current_item.clear(); } } else { current_item += c; } } // Add the last item if not empty if (!current_item.empty()) { std::string final = dequote(trim(current_item)); if (!final.empty()) result.push_back(final); } return result; } int str2int(const std::string &str) { try { return std::stoi(str); } catch (const std::exception& e) { error << "Invalid integer string: [" << str << "]" << std::endl; return 0; } } void recursive_copy(const std::string & source, const std::string & destination) { try { if (std::filesystem::is_directory(source)) { if (!std::filesystem::exists(destination)) { std::filesystem::create_directory(destination); } for (const auto& entry : std::filesystem::directory_iterator(source)) { recursive_copy(entry.path(), destination / entry.path().filename()); } } else if (std::filesystem::is_regular_file(source)) { std::filesystem::copy(source, destination, std::filesystem::copy_options::overwrite_existing); } } catch (const std::filesystem::filesystem_error& ex) { std::cerr << "Error copying " << source << " to " << destination << ": " << ex.what() << std::endl; } } void ensure_directories_exist(std::vector directories) { for (const auto& directory : directories) { if (!std::filesystem::exists(directory)) { std::filesystem::create_directories(directory); } } } //https://www.geeksforgeeks.org/kmp-algorithm-for-pattern-searching/ void constructLps(const std::string &pat, std::vector &lps) { // len stores the length of longest prefix which // is also a suffix for the previous index int len = 0; // lps[0] is always 0 lps[0] = 0; int i = 1; while (i < pat.length()) { // If characters match, increment the size of lps if (pat[i] == pat[len]) { len++; lps[i] = len; i++; } // If there is a mismatch else { if (len != 0) { // Update len to the previous lps value // to avoid reduntant comparisons len = lps[len - 1]; } else { // If no matching prefix found, set lps[i] to 0 lps[i] = 0; i++; } } } } std::vector search(const std::string &pat, const std::string &txt) { int n = txt.length(); int m = pat.length(); std::vector lps(m); std::vector res; constructLps(pat, lps); // Pointers i and j, for traversing // the text and pattern int i = 0; int j = 0; while (i < n) { // If characters match, move both pointers forward if (txt[i] == pat[j]) { i++; j++; // If the entire pattern is matched // store the start index in result if (j == m) { res.push_back(i - j); // Use LPS of previous index to // skip unnecessary comparisons j = lps[j - 1]; } } // If there is a mismatch else { // Use lps value of previous index // to avoid redundant comparisons if (j != 0) j = lps[j - 1]; else i++; } } return res; } int count_substring(const std::string &substring, const std::string &text) { std::vector positions = search(substring, text); return positions.size(); } std::vector split(const std::string& str, const std::string& delimiter) { std::vector tokens; size_t start = 0; size_t end = 0; while ((end = str.find(delimiter, start)) != std::string::npos) { tokens.push_back(str.substr(start, end - start)); start = end + delimiter.length(); } // Add the last token tokens.push_back(str.substr(start)); return tokens; } std::string random_alphanumeric_string(int length) { static std::mt19937 generator(std::random_device{}()); static const std::string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; std::uniform_int_distribution<> distribution(0, chars.size() - 1); std::string random_string; for (int i = 0; i < length; ++i) { random_string += chars[distribution(generator)]; } return random_string; } std::string requote(std::string str) { return quote(trim(dequote(trim(str)))); } int return_die(const std::string & msg) { error << "Fatal error:" << std::endl; error << msg << std::endl; return 1; } std::string safearg(const std::vector & args, int index) { if (index<0 || index >= args.size()) return ""; return args[index]; } std::string safearg(int argc, char *argv[], int index) { if (index<0 || index >= argc) return ""; return argv[index]; } std::string left_align(const std::string & str, int width) { if (static_cast(str.size()) >= width) return str; return str + std::string(width - str.size(), ' '); } std::string right_align(const std::string & str, int width) { if (static_cast(str.size()) >= width) return str; return std::string(width - str.size(), ' ') + str; } std::string center_align(const std::string & str, int width) { int pad = width - static_cast(str.size()); if (pad <= 0) return str; int pad_left = pad / 2; int pad_right = pad - pad_left; return std::string(pad_left, ' ') + str + std::string(pad_right, ' '); } std::string replace_with_environment_variables_like_bash(std::string str) { // Combined regex pattern for both ${var} and $var formats std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))"); std::string result = str; std::smatch match; while (std::regex_search(result, match, var_pattern)) { // match[1] will contain capture from ${var} format // match[2] will contain capture from $var format std::string var_name = match[1].matched ? match[1].str() : match[2].str(); // Get value from system environment variables const char* env_value = std::getenv(var_name.c_str()); std::string value = env_value ? env_value : ""; result = result.replace(match.position(), match.length(), value); } // dequote the result return result; } std::string substitute_provided_key_value_pairs(std::string str, const ordered_env_vars& env_vars) { // Combined regex pattern for both ${var} and $var formats std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))"); std::string result = str; std::smatch match; while (std::regex_search(result, match, var_pattern)) { // match[1] will contain capture from ${var} format // match[2] will contain capture from $var format std::string var_name = match[1].matched ? match[1].str() : match[2].str(); // Get value from environment variables std::string value = get_var(env_vars, var_name); result = result.replace(match.position(), match.length(), value); } return result; } int get_console_width() { struct winsize w; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) { return w.ws_col; } // Fallback to a reasonable default if we can't get the width return 80; } std::string remove_return(std::string str) { str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); return str; } std::string get_line_wrap(std::string &src, int maxchars) { if (src.empty()) return ""; if (src.length() <= maxchars) { std::string out = src; src.erase(); return remove_return(out) + '\n'; } // find last whitespace up to but not more than maxchars size_t grab_to=maxchars; size_t lastreturn = src.rfind('\n', maxchars); size_t lastspace = src.rfind(' ', maxchars); if (lastreturn != std::string::npos) grab_to = lastreturn; else if (lastspace != std::string::npos) grab_to = lastspace; std::string out = src.substr(0, grab_to); src = src.substr(grab_to + 1); return remove_return(out) + '\n'; } std::string tolower(const std::string& str) { if (str.empty()) return str; std::string result; result.reserve(str.size()); // Pre-allocate space for efficiency for (unsigned char c : str) { result.push_back(std::tolower(c)); } return result; } // Common utility function to make HTTP requests struct HttpResult { bool success; int status; std::string body; std::string error; }; HttpResult make_http_request(const std::string& url) { try { // Parse the URL to get host and path std::string host; std::string path; size_t protocol_end = url.find("://"); if (protocol_end != std::string::npos) { size_t host_start = protocol_end + 3; size_t path_start = url.find('/', host_start); if (path_start != std::string::npos) { host = url.substr(host_start, path_start - host_start); path = url.substr(path_start); } else { host = url.substr(host_start); path = "/"; } } else { return {false, 0, "", "Invalid URL format"}; } // Create HTTP or HTTPS client based on protocol std::string protocol = url.substr(0, protocol_end); std::unique_ptr cli; if (protocol == "https") { // For HTTPS, include the full scheme and host cli = std::make_unique("https://" + host); } else if (protocol == "http") { cli = std::make_unique("http://" + host); } else { return {false, 0, "", "Unsupported protocol: " + protocol}; } cli->set_connection_timeout(10); // 10 second timeout cli->set_read_timeout(30); // 30 second read timeout // Make GET request auto res = cli->Get(path); if (!res) { return {false, 0, "", "Failed to connect to server"}; } if (res->status != 200) { return {false, res->status, "", "HTTP request failed with status " + std::to_string(res->status)}; } return {true, res->status, res->body, ""}; } catch (const std::exception& e) { return {false, 0, "", std::string("Exception: ") + e.what()}; } } bool download_file(const std::string &url, const std::string &destination) { // For HTTPS URLs, use curl as it handles SSL properly in static builds if (url.substr(0, 8) == "https://") { std::string cmd = "curl -fsSL " + quote(url) + " -o " + quote(destination) + " 2>/dev/null"; int result = system(cmd.c_str()); if (result != 0) { warning << "Failed to download file from URL: " << url << std::endl; return false; } // Check if file was actually created std::ifstream check_file(destination); return check_file.good(); } // For HTTP URLs, use the built-in httplib auto result = make_http_request(url); if (!result.success) { warning << "Failed to download file from URL: " << url << std::endl; return false; } try { std::ofstream out_file(destination, std::ios::binary); if (!out_file) { warning << "Failed to open file for writing: " << destination << std::endl; return false; } out_file.write(result.body.c_str(), result.body.size()); out_file.close(); return true; } catch (const std::exception& e) { warning << "Failed to download file from URL: " << url << std::endl; warning << "Exception: " << e.what() << std::endl; return false; } } nlohmann::json get_json_from_url(const std::string &url) { auto result = make_http_request(url); if (!result.success) { warning << "Failed to get JSON from URL: " << url << std::endl; return nlohmann::json(); } try { return nlohmann::json::parse(result.body); } catch (const nlohmann::json::parse_error& e) { warning << "Failed to parse JSON from URL: " << url << std::endl; warning << "JSON: " << result.body << std::endl; return nlohmann::json(); } } std::string get_string_from_url(const std::string &url) { auto result = make_http_request(url); if (!result.success) { warning << "Failed to get string from URL: " << url << std::endl; return std::string(); } return result.body; } bool match_line(const std::string &line, const std::string &pattern) { return trim(line) == trim(pattern); } // replace or append a block of text to a file, matching first and last lines if replacing. // edits file in-place. bool file_replace_or_add_segment(std::string filepath, std::string segment) { // Create a backup of the original file std::string backup_path = filepath + ".bak"; try { std::filesystem::copy_file(filepath, backup_path, std::filesystem::copy_options::overwrite_existing); } catch (const std::exception& e) { std::cerr << "Error creating backup file: " << e.what() << std::endl; return false; } // Handle empty segment if (segment.empty()) { error << "Empty segment provided" << std::endl; return false; } // split the segment into lines std::vector segment_lines = split(segment, "\n"); // remove empty lines segment_lines.erase(std::remove_if(segment_lines.begin(), segment_lines.end(), [](const std::string& line) { return trim(line).empty(); }), segment_lines.end()); // remove any lines that are just whitespace segment_lines.erase(std::remove_if(segment_lines.begin(), segment_lines.end(), [](const std::string& line) { return trim(line).empty(); }), segment_lines.end()); // check that the segment has at least two lines if (segment_lines.size() < 2) { error << "Segment must contain at least two non-empty lines" << std::endl; return false; } // Read the entire file into memory std::ifstream input_file(filepath); if (!input_file.is_open()) { error << "Unable to open file: " << filepath << std::endl; return false; } std::vector file_lines; std::string line; while (std::getline(input_file, line)) { file_lines.push_back(line); } input_file.close(); // Store original file size for verification size_t original_size = file_lines.size(); if (original_size == 0) { warning << "File is empty" << std::endl; } // Try to find the matching block bool found_match = false; for (size_t i = 0; i < file_lines.size(); i++) { if (match_line(file_lines[i], segment_lines[0])) { // Found potential start, look for end for (size_t j = i + 1; j < file_lines.size(); j++) { if (match_line(file_lines[j], segment_lines[segment_lines.size() - 1])) { // Found matching block, replace it file_lines.erase(file_lines.begin() + i, file_lines.begin() + j + 1); file_lines.insert(file_lines.begin() + i, segment_lines.begin(), segment_lines.end()); found_match = true; break; } } if (found_match) break; } } // If no match found, append the segment if (!found_match) { file_lines.insert(file_lines.end(), segment_lines.begin(), segment_lines.end()); } // Write back to file std::ofstream output_file(filepath); if (!output_file.is_open()) { error << "Unable to open file for writing: " << filepath << std::endl; return false; } for (const auto& line : file_lines) { output_file << line << "\n"; } output_file.close(); // If everything succeeded, remove the backup try { std::filesystem::remove(backup_path); } catch (const std::exception& e) { warning << "Could not remove backup file: " << e.what() << std::endl; } return true; } bool legal_service_name(const std::string &service_name) { static bool initialized = false; static bool legal_chars[256] = {false}; // Initialize all to false // One-time initialization if (!initialized) { // Set true for valid characters for (unsigned char c : "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "._-") { legal_chars[c] = true; } initialized = true; } return std::all_of(service_name.begin(), service_name.end(), [](unsigned char c) { return legal_chars[c]; }); } } // namespace dropshell