#include "exploit_cli.h" #include #include // Function to check if a path is a directory int is_directory(const char *path) { struct stat path_stat; if (stat(path, &path_stat) != 0) { return 0; // Error accessing path } return S_ISDIR(path_stat.st_mode); } // Function to upload a single file to repository int upload_single_file(const char *file_path, const char *relative_path, const char *repo_name, const char *token, int replace) { CURL *curl = curl_easy_init(); if (!curl) { print_error("Failed to initialize CURL"); return 0; } char url[MAX_PATH]; snprintf(url, MAX_PATH, "%s/repository/upload", API_BASE_URL); // If replace flag is set, try to delete the file first if (replace) { char delete_url[MAX_PATH]; snprintf(delete_url, MAX_PATH, "%s/repository/delete/", API_BASE_URL); char json_payload[MAX_BUFFER]; snprintf(json_payload, MAX_BUFFER, "{\"name\":\"%s\",\"path\":\"%s\"}", repo_name, relative_path); // Attempting to delete existing file // Send delete request using HTTP POST method http_response_t delete_response = http_post(delete_url, token, json_payload); if (delete_response.data) { // Processing delete response free(delete_response.data); } } // Create multipart form data curl_mime *mime = curl_mime_init(curl); curl_mimepart *part; // Add repository name part = curl_mime_addpart(mime); curl_mime_name(part, "repo"); curl_mime_data(part, repo_name, CURL_ZERO_TERMINATED); // Sending repo_name to API // Add path parameter only if file is in subfolder (contains '/') if (strchr(relative_path, '/') != NULL) { // Extract directory path without the filename char dir_path[MAX_PATH]; strncpy(dir_path, relative_path, MAX_PATH - 1); dir_path[MAX_PATH - 1] = '\0'; // Find the last slash and terminate the string there to get just the directory char *last_slash = strrchr(dir_path, '/'); if (last_slash) { *last_slash = '\0'; part = curl_mime_addpart(mime); curl_mime_name(part, "path"); curl_mime_data(part, dir_path, CURL_ZERO_TERMINATED); // Sending path to API } } // Add file part = curl_mime_addpart(mime); curl_mime_name(part, "files[]"); curl_mime_filedata(part, file_path); curl_mime_filename(part, basename((char *)file_path)); // Set up headers struct curl_slist *headers = NULL; char auth_header[MAX_BUFFER]; snprintf(auth_header, MAX_BUFFER, "Authorization: Bearer %s", token); headers = curl_slist_append(headers, auth_header); // Set up response buffer http_response_t response = {0}; // Configure CURL curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // Perform the request CURLcode res = curl_easy_perform(curl); // Check for errors int success = 0; if (res != CURLE_OK) { char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "Upload failed: %s", curl_easy_strerror(res)); print_error(error_msg); } else { long response_code; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); if (response_code == 200) { success = 1; // Processing API response } else if (response_code == 500 && response.data && strstr(response.data, "Duplicate entry")) { print_info("🔄 Duplicate detected, replacing file..."); // Cleanup current request curl_slist_free_all(headers); curl_mime_free(mime); curl_easy_cleanup(curl); if (response.data) { free(response.data); } // Retry with replace flag return upload_single_file(file_path, relative_path, repo_name, token, 1); } else { char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "Upload failed with HTTP code: %ld", response_code); print_error(error_msg); if (response.data) { char response_msg[512]; snprintf(response_msg, sizeof(response_msg), "Response: %s", response.data); print_error(response_msg); } } } // Cleanup curl_slist_free_all(headers); curl_mime_free(mime); curl_easy_cleanup(curl); if (response.data) { free(response.data); } return success; } // Upload command implementation int cmd_upload(int argc, char *argv[]) { if (argc < 2) { printf("\033[33m📤 Usage: exp upload [--path ] [--replace]\033[0m\n"); printf("\033[37mOptions:\033[0m\n"); printf("\033[90m --path Upload to specific path in repository\033[0m\n"); printf("\033[90m --replace Delete existing files/folders before upload\033[0m\n"); return 1; } printf("\033[36m╔══════════════════════════════════════╗\033[0m\n"); printf("\033[36m║ 📤 File Upload ║\033[0m\n"); printf("\033[36m╚══════════════════════════════════════╝\033[0m\n"); // Parse arguments char *path = NULL; int file_count = 0; char *files[MAX_BUFFER]; int replace = 0; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--path") == 0) { if (i + 1 < argc) { path = argv[i + 1]; i++; // Skip the next argument which is the path value } else { print_error("--path requires a value"); return 1; } } else if (strcmp(argv[i], "--replace") == 0) { replace = 1; } else { files[file_count++] = argv[i]; } } if (file_count == 0) { print_error("No files specified for upload"); return 1; } // Check token char *token = get_token(); if (!token) { print_error("You are not logged in. Use 'exp login' first"); return 1; } // Check repository configuration char repo_config_path[MAX_PATH]; snprintf(repo_config_path, MAX_PATH, "%s/repo_config", get_config_dir()); FILE *fp = fopen(repo_config_path, "r"); if (!fp) { print_error("Repository not initialized. Use 'exp init' first"); free(token); return 1; } char config_line[MAX_BUFFER]; char repo_name[MAX_BUFFER] = {0}; char username[MAX_BUFFER] = {0}; while (fgets(config_line, MAX_BUFFER, fp)) { if (strncmp(config_line, "name=", 5) == 0) { strcpy(repo_name, config_line + 5); // Remove newline if present size_t len = strlen(repo_name); if (len > 0 && repo_name[len-1] == '\n') { repo_name[len-1] = '\0'; } } else if (strncmp(config_line, "username=", 9) == 0) { strcpy(username, config_line + 9); // Remove newline if present size_t len = strlen(username); if (len > 0 && username[len-1] == '\n') { username[len-1] = '\0'; } } } fclose(fp); if (strlen(repo_name) == 0 || strlen(username) == 0) { print_error("Incomplete repository information"); free(token); return 1; } int total_files = 0; // Process each file or folder for (int i = 0; i < file_count; i++) { const char *current_file = files[i]; struct stat file_stat; if (stat(current_file, &file_stat) != 0) { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "Cannot access %s, skipping", current_file); print_warning(warning_msg); continue; } if (S_ISREG(file_stat.st_mode)) { // Single file upload // Get the base name of the file char file_name_buffer[MAX_PATH]; strncpy(file_name_buffer, current_file, MAX_PATH - 1); file_name_buffer[MAX_PATH - 1] = '\0'; const char *base_name = basename(file_name_buffer); // Construct the relative path based on whether a path parameter was provided char relative_path[MAX_PATH]; if (path) { snprintf(relative_path, MAX_PATH, "%s/%s", path, base_name); } else { snprintf(relative_path, MAX_PATH, "%s", base_name); } // Upload file individually if (upload_single_file(current_file, relative_path, repo_name, token, replace)) { char info_msg[256]; snprintf(info_msg, sizeof(info_msg), "✓ Uploaded: %s", current_file); print_info(info_msg); total_files++; } else { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "✗ Failed: %s", current_file); print_warning(warning_msg); } } else if (S_ISDIR(file_stat.st_mode)) { // Process folder recursively DIR *dir = opendir(current_file); if (!dir) { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "Failed to open folder %s, skipping", current_file); print_warning(warning_msg); continue; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // Skip . and .. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } char full_path[MAX_PATH]; snprintf(full_path, MAX_PATH, "%s/%s", current_file, entry->d_name); struct stat entry_stat; if (stat(full_path, &entry_stat) != 0) { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "Cannot access %s, skipping", full_path); print_warning(warning_msg); continue; } if (S_ISREG(entry_stat.st_mode)) { // Calculate relative path for the file char relative_path[MAX_PATH]; // For files in root folder, use just the filename // Construct the relative path based on whether a path parameter was provided if (path) { snprintf(relative_path, MAX_PATH, "%s/%s", path, entry->d_name); } else { snprintf(relative_path, MAX_PATH, "%s", entry->d_name); } // Upload file individually if (upload_single_file(full_path, relative_path, repo_name, token, replace)) { char info_msg[256]; snprintf(info_msg, sizeof(info_msg), "✓ Uploaded: %s", full_path); print_info(info_msg); total_files++; } else { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "✗ Failed: %s", full_path); print_warning(warning_msg); } } else if (S_ISDIR(entry_stat.st_mode)) { // Recursively process subfolder DIR *subdir = opendir(full_path); if (!subdir) { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "Failed to open subfolder %s, skipping", full_path); print_warning(warning_msg); continue; } char info_msg[256]; snprintf(info_msg, sizeof(info_msg), "📁 Processing folder: %s", full_path); print_info(info_msg); struct dirent *subentry; while ((subentry = readdir(subdir)) != NULL) { // Skip . and .. if (strcmp(subentry->d_name, ".") == 0 || strcmp(subentry->d_name, "..") == 0) { continue; } char sub_full_path[MAX_PATH]; snprintf(sub_full_path, MAX_PATH, "%s/%s", full_path, subentry->d_name); struct stat sub_entry_stat; if (stat(sub_full_path, &sub_entry_stat) != 0) { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "Cannot access %s, skipping", sub_full_path); print_warning(warning_msg); continue; } if (S_ISREG(sub_entry_stat.st_mode)) { // Calculate relative path for the file char relative_path[MAX_PATH]; // Calculate relative path for the file in subfolder const char *subdir_name = basename((char *)full_path); // Construct the relative path based on whether a path parameter was provided if (path) { snprintf(relative_path, MAX_PATH, "%s/%s/%s", path, subdir_name, subentry->d_name); } else { snprintf(relative_path, MAX_PATH, "%s/%s", subdir_name, subentry->d_name); } // Calculate relative path for file // Upload file individually if (upload_single_file(sub_full_path, relative_path, repo_name, token, replace)) { char info_msg[256]; snprintf(info_msg, sizeof(info_msg), "✓ Uploaded: %s", sub_full_path); print_info(info_msg); total_files++; } else { char warning_msg[256]; snprintf(warning_msg, sizeof(warning_msg), "✗ Failed: %s", sub_full_path); print_warning(warning_msg); } } } closedir(subdir); } } closedir(dir); } } if (total_files == 0) { print_error("No valid files found for upload"); free(token); return 1; } printf("\n"); print_success("Upload completed successfully!"); printf("\033[36m╔══════════════════════════════════════╗\033[0m\n"); printf("\033[36m║ 🎉 Upload Complete! ║\033[0m\n"); printf("\033[36m╚══════════════════════════════════════╝\033[0m\n"); printf("\033[37m📊 Total files uploaded: \033[32m%d\033[0m\n", total_files); printf("\033[90m💡 Files are now available in your repository\033[0m\n"); printf("\033[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n"); // Cleanup free(token); return 0; } // Release command implementation int cmd_release(int argc, char *argv[]) { if (argc < 3) { printf("\033[33m🚀 Usage: exp release [description] [file]\033[0m\n"); printf("\033[37mParameters:\033[0m\n"); printf("\033[90m <tag_name> Version tag (use hyphens instead of dots, e.g., v1-0-0)\033[0m\n"); printf("\033[90m <title> Release title\033[0m\n"); printf("\033[90m [description] Optional release description\033[0m\n"); printf("\033[90m [file] Optional file to include in the release\033[0m\n"); printf("\033[37mExample:\033[0m\n"); printf("\033[90m exp release v1-0-0 \"Initial Release\" \"First stable release\" ./example.txt\033[0m\n"); return 1; } printf("\033[36m╔══════════════════════════════════════╗\033[0m\n"); printf("\033[36m║ 🚀 Creating Release ║\033[0m\n"); printf("\033[36m╚══════════════════════════════════════╝\033[0m\n"); // Check token char *token = get_token(); if (!token) { print_error("You are not logged in. Use 'exp login' first"); return 1; } // Check repository configuration char repo_config_path[MAX_PATH]; snprintf(repo_config_path, MAX_PATH, "%s/repo_config", get_config_dir()); FILE *fp = fopen(repo_config_path, "r"); if (!fp) { print_error("Repository not initialized. Use 'exp init' first"); free(token); return 1; } char config_line[MAX_BUFFER]; char repo_name[MAX_BUFFER] = {0}; char username[MAX_BUFFER] = {0}; while (fgets(config_line, MAX_BUFFER, fp)) { if (strncmp(config_line, "name=", 5) == 0) { strcpy(repo_name, config_line + 5); // Remove newline if present size_t len = strlen(repo_name); if (len > 0 && repo_name[len-1] == '\n') { repo_name[len-1] = '\0'; } } else if (strncmp(config_line, "username=", 9) == 0) { strcpy(username, config_line + 9); // Remove newline if present size_t len = strlen(username); if (len > 0 && username[len-1] == '\n') { username[len-1] = '\0'; } } } fclose(fp); if (strlen(repo_name) == 0 || strlen(username) == 0) { print_error("Incomplete repository information"); free(token); return 1; } const char *tag_name = argv[1]; const char *title = argv[2]; const char *description = (argc > 3) ? argv[3] : ""; const char *filepath = (argc > 4) ? argv[4] : NULL; // Create URL for release char url[MAX_PATH]; snprintf(url, MAX_PATH, "%s/release/upload", API_BASE_URL); if (filepath) { // Check if file exists struct stat path_stat; if (stat(filepath, &path_stat) != 0 || !S_ISREG(path_stat.st_mode)) { print_error("File not found or not a regular file"); free(token); return 1; } // Upload file with multipart form data CURL *curl; CURLcode res; http_response_t response = {0}; response.data = malloc(1); response.size = 0; curl = curl_easy_init(); if (curl) { struct curl_slist *headers = NULL; if (token) { char auth_header[MAX_BUFFER]; snprintf(auth_header, MAX_BUFFER, "Authorization: Bearer %s", token); headers = curl_slist_append(headers, auth_header); } curl_mime *mime = curl_mime_init(curl); curl_mimepart *part; // Tambahkan tag_name part = curl_mime_addpart(mime); curl_mime_name(part, "tag_name"); curl_mime_data(part, tag_name, CURL_ZERO_TERMINATED); // Tambahkan title part = curl_mime_addpart(mime); curl_mime_name(part, "title"); curl_mime_data(part, title, CURL_ZERO_TERMINATED); // Tambahkan description part = curl_mime_addpart(mime); curl_mime_name(part, "description"); curl_mime_data(part, description, CURL_ZERO_TERMINATED); // Tambahkan repository part = curl_mime_addpart(mime); curl_mime_name(part, "repository"); curl_mime_data(part, repo_name, CURL_ZERO_TERMINATED); // Tambahkan username part = curl_mime_addpart(mime); curl_mime_name(part, "username"); curl_mime_data(part, username, CURL_ZERO_TERMINATED); // Tambahkan file part = curl_mime_addpart(mime); curl_mime_name(part, "release_files[]"); curl_mime_filedata(part, filepath); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response); res = curl_easy_perform(curl); if (res != CURLE_OK) { print_error(curl_easy_strerror(res)); curl_mime_free(mime); curl_slist_free_all(headers); curl_easy_cleanup(curl); free(response.data); free(token); return 1; } curl_mime_free(mime); curl_slist_free_all(headers); curl_easy_cleanup(curl); } else { print_error("Failed to initialize curl"); free(token); return 1; } // Parse JSON response struct json_object *parsed_json; struct json_object *status_obj; struct json_object *message_obj; parsed_json = json_tokener_parse(response.data); if (!parsed_json) { print_error("Failed to parse JSON response"); free(response.data); free(token); return 1; } // Check response status json_object_object_get_ex(parsed_json, "status", &status_obj); json_object_object_get_ex(parsed_json, "message", &message_obj); const char *status = json_object_get_string(status_obj); const char *message = json_object_get_string(message_obj); if (strcmp(status, "success") != 0) { print_error(message); json_object_put(parsed_json); free(response.data); free(token); return 1; } printf("\n"); print_success("Release created successfully!"); printf("\033[36m╔══════════════════════════════════════╗\033[0m\n"); printf("\033[36m║ 🎉 Release Published! ║\033[0m\n"); printf("\033[36m╚══════════════════════════════════════╝\033[0m\n"); printf("\033[37m🏷️ Tag: \033[32m%s\033[0m\n", tag_name); printf("\033[37m📝 Title: \033[90m%s\033[0m\n", title); if (filepath) { printf("\033[37m📎 File: \033[90m%s\033[0m\n", filepath); } printf("\033[90m💡 Your release is now available for download\033[0m\n"); printf("\033[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n"); // Clean up memory json_object_put(parsed_json); free(response.data); } else { // Create JSON for release request without file char json_data[MAX_BUFFER]; snprintf(json_data, MAX_BUFFER, "{\"tag_name\":\"%s\",\"title\":\"%s\",\"description\":\"%s\",\"repository\":\"%s\",\"username\":\"%s\"}", tag_name, title, description, repo_name, username); // Send release request http_response_t response = http_post(url, token, json_data); if (!response.data) { print_error("Failed to create release"); free(token); return 1; } // Parse JSON response struct json_object *parsed_json; struct json_object *status_obj; struct json_object *message_obj; parsed_json = json_tokener_parse(response.data); if (!parsed_json) { print_error("Failed to parse JSON response"); free(response.data); free(token); return 1; } // Check response status json_object_object_get_ex(parsed_json, "status", &status_obj); json_object_object_get_ex(parsed_json, "message", &message_obj); const char *status = json_object_get_string(status_obj); const char *message = json_object_get_string(message_obj); if (strcmp(status, "success") != 0) { print_error(message); json_object_put(parsed_json); free(response.data); free(token); return 1; } print_success("Release created successfully"); char tag_msg[256]; snprintf(tag_msg, sizeof(tag_msg), "Tag: %s", tag_name); print_info(tag_msg); char title_msg[256]; snprintf(title_msg, sizeof(title_msg), "Title: %s", title); print_info(title_msg); // Clean up memory json_object_put(parsed_json); free(response.data); } free(token); return 0; }