#include #include #include #include #include #include #include #include #include #include "markdown_translator.hpp" #include "file_uploader.hpp" #include "rclone_uploader.hpp" bool parseArguments(int argc, char* argv[], std::unordered_map& params, std::string& inputFile) { if (argc < 2) { std::cerr << "Usage: COMMAND [-o ] [-css ] [--encode ] [other options]\n"; return false; } params["-o"] = "index.html"; params["-css"] = "styles/ffxiv-style.css"; for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg[0] == '-') { if (i + 1 < argc) { params[arg] = argv[i + 1]; ++i; } else { std::cerr << "Error: Missing value for option " << arg << "\n"; return false; } } else { if (inputFile.empty()) { inputFile = arg; } else { std::cerr << "Error: Multiple input files specified: " << inputFile << " and " << arg << "\n"; return false; } } } if (inputFile.empty()) { std::cerr << "Error: No input file specified\n"; return false; } return true; } // Encrypt using AES-256-GCM with PBKDF2-HMAC-SHA256 key derivation. static std::string base64Encode(const std::vector& data) { if (data.empty()) return std::string(); size_t out_len = 4 * ((data.size() + 2) / 3) + 1; std::vector out(out_len); int written = EVP_EncodeBlock(out.data(), data.data(), (int)data.size()); return std::string(reinterpret_cast(out.data()), written); } static std::vector encryptAESGCM(const std::string& plaintext, const std::string& passphrase) { const int SALT_LEN = 16; const int IV_LEN = 12; const int TAG_LEN = 16; const int KEY_LEN = 32; const int PBKDF2_ITER = 100000; std::vector salt(SALT_LEN); if (RAND_bytes(salt.data(), SALT_LEN) != 1) { throw std::runtime_error("RAND_bytes for salt failed"); } std::vector iv(IV_LEN); if (RAND_bytes(iv.data(), IV_LEN) != 1) { throw std::runtime_error("RAND_bytes for iv failed"); } std::vector key(KEY_LEN); if (PKCS5_PBKDF2_HMAC(passphrase.c_str(), (int)passphrase.size(), salt.data(), SALT_LEN, PBKDF2_ITER, EVP_sha256(), KEY_LEN, key.data()) != 1) { throw std::runtime_error("PBKDF2 key derivation failed"); } EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) throw std::runtime_error("EVP_CIPHER_CTX_new failed"); int rc = EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL); if (rc != 1) { EVP_CIPHER_CTX_free(ctx); throw std::runtime_error("EVP_EncryptInit_ex failed"); } rc = EVP_EncryptInit_ex(ctx, NULL, NULL, key.data(), iv.data()); if (rc != 1) { EVP_CIPHER_CTX_free(ctx); throw std::runtime_error("EVP_EncryptInit_ex set key/iv failed"); } std::vector ciphertext(plaintext.size()); int outlen = 0; rc = EVP_EncryptUpdate(ctx, ciphertext.data(), &outlen, reinterpret_cast(plaintext.data()), (int)plaintext.size()); if (rc != 1) { EVP_CIPHER_CTX_free(ctx); throw std::runtime_error("EVP_EncryptUpdate failed"); } int tmplen = 0; rc = EVP_EncryptFinal_ex(ctx, ciphertext.data() + outlen, &tmplen); if (rc != 1) { EVP_CIPHER_CTX_free(ctx); throw std::runtime_error("EVP_EncryptFinal_ex failed"); } int cipher_len = outlen + tmplen; ciphertext.resize(cipher_len); std::vector tag(TAG_LEN); rc = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, TAG_LEN, tag.data()); if (rc != 1) { EVP_CIPHER_CTX_free(ctx); throw std::runtime_error("EVP_CIPHER_CTX_ctrl GET_TAG failed"); } EVP_CIPHER_CTX_free(ctx); std::vector out; out.reserve(salt.size() + iv.size() + ciphertext.size() + tag.size()); out.insert(out.end(), salt.begin(), salt.end()); out.insert(out.end(), iv.begin(), iv.end()); out.insert(out.end(), ciphertext.begin(), ciphertext.end()); out.insert(out.end(), tag.begin(), tag.end()); return out; } int main(int argc, char* argv[]) { std::unordered_map params; std::string inputFile; // FileUploader* fileupload; // fileupload = new RcloneUploader("", "r2"); // std::cout << fileupload->testConnection() << std::endl; if (!parseArguments(argc, argv, params, inputFile)) { return 1; } std::ifstream file(inputFile); if (!file) { std::cerr << "Error: Could not open file " << inputFile << "\n"; return 1; } // Read entire file content std::stringstream buffer; buffer << file.rdbuf(); std::string markdownContent = buffer.str(); file.close(); // Create translator and convert markdown to HTML MarkdownTranslator translator; // Get CSS path from parameters or use default std::string cssPath = ""; if (params.find("-css") != params.end()) { cssPath = params["-css"]; } std::string htmlOutput = translator.translate(markdownContent); if (params.find("--encode") != params.end() && !params["--encode"].empty()) { try { std::string startMarker = "
"; size_t startPos = htmlOutput.find(startMarker); if (startPos != std::string::npos) { const std::string endMarker = ""; size_t endPos = htmlOutput.find(endMarker, startPos); if (endPos != std::string::npos) { std::string contentBlock = htmlOutput.substr(startPos, endPos - startPos); std::vector encrypted = encryptAESGCM(contentBlock, params["--encode"]); std::string b64 = base64Encode(encrypted); std::stringstream repl; repl << "
\n"; repl << "
\n"; repl << "
🔒
\n"; repl << "

Encoded Content

\n"; repl << "

Enter the passphrase to decode this post.

\n"; repl << " \n"; repl << "
\n"; repl << "
\n"; repl << "
\n"; repl << "\n"; htmlOutput = htmlOutput.substr(0, startPos) + repl.str() + htmlOutput.substr(endPos + endMarker.length()); } } } catch (const std::exception& ex) { } } // Always strip the WIKI_CONTENT_END marker when not encrypted { const std::string marker = ""; size_t pos = htmlOutput.find(marker); if (pos != std::string::npos) htmlOutput.erase(pos, marker.length()); } // Write to output file std::string outputFile = params["-o"]; std::ofstream outFile(outputFile); if (!outFile) { std::cerr << "Error: Could not open output file " << outputFile << "\n"; return 1; } outFile << htmlOutput; outFile.close(); std::cout << "HTML output written to " << outputFile << std::endl; return 0; }