diff --git a/src/browser.cpp b/src/browser.cpp index 6489886..bccf3f7 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include class Browser::Impl { public: @@ -207,42 +209,98 @@ public: } } + // URL encode helper function + std::string url_encode(const std::string& value) { + std::string result; + for (unsigned char c : value) { + if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + result += c; + } else if (c == ' ') { + result += '+'; + } else { + char hex[4]; + snprintf(hex, sizeof(hex), "%%%02X", c); + result += hex; + } + } + return result; + } + void submit_form(DomNode* button) { status_message = "Submitting form..."; - // Simple GET implementation for now + + // Find parent form DomNode* form = button->parent; while (form && form->element_type != ElementType::FORM) form = form->parent; - + if (!form) { status_message = "Error: Button not in a form"; return; } - // Collect data - std::string query_string; + // Collect form data with URL encoding + std::string form_data; for (DomNode* field : current_tree.form_fields) { // Check if field belongs to this form DomNode* p = field->parent; bool is_child = false; while(p) { if(p == form) { is_child = true; break; } p = p->parent; } - + if (is_child && !field->name.empty()) { - if (!query_string.empty()) query_string += "&"; - query_string += field->name + "=" + field->value; + if (!form_data.empty()) form_data += "&"; + form_data += url_encode(field->name) + "=" + url_encode(field->value); } } std::string target_url = form->action; if (target_url.empty()) target_url = current_url; - // TODO: Handle POST. For now, assume GET or append query string - if (target_url.find('?') == std::string::npos) { - target_url += "?" + query_string; - } else { - target_url += "&" + query_string; - } + // Check form method (default to GET if not specified) + std::string method = form->method; + std::transform(method.begin(), method.end(), method.begin(), ::toupper); - load_page(target_url); + if (method == "POST") { + // POST request + status_message = "Sending POST request..."; + HttpResponse response = http_client.post(target_url, form_data); + + if (!response.error_message.empty()) { + status_message = "Error: " + response.error_message; + return; + } + + if (!response.is_success()) { + status_message = "Error: HTTP " + std::to_string(response.status_code); + return; + } + + // Parse and render response + DocumentTree tree = html_parser.parse_tree(response.body, target_url); + current_tree = std::move(tree); + current_url = target_url; + rendered_lines = renderer.render_tree(current_tree, screen_width); + build_interactive_list(); + scroll_pos = 0; + current_element_index = -1; + + // Update history + if (history_pos < static_cast(history.size()) - 1) { + history.erase(history.begin() + history_pos + 1, history.end()); + } + history.push_back(current_url); + history_pos = history.size() - 1; + + status_message = "Form submitted (POST)"; + } else { + // GET request (default) + if (target_url.find('?') == std::string::npos) { + target_url += "?" + form_data; + } else { + target_url += "&" + form_data; + } + load_page(target_url); + status_message = "Form submitted (GET)"; + } } void draw_status_bar() { diff --git a/src/http_client.cpp b/src/http_client.cpp index c959af0..0e0eff8 100644 --- a/src/http_client.cpp +++ b/src/http_client.cpp @@ -117,6 +117,95 @@ HttpResponse HttpClient::fetch(const std::string& url) { return response; } +HttpResponse HttpClient::post(const std::string& url, const std::string& data, + const std::string& content_type) { + HttpResponse response; + response.status_code = 0; + + if (!pImpl->curl) { + response.error_message = "CURL not initialized"; + return response; + } + + curl_easy_reset(pImpl->curl); + + // Re-apply settings + curl_easy_setopt(pImpl->curl, CURLOPT_URL, url.c_str()); + + // Set write callback + std::string response_body; + curl_easy_setopt(pImpl->curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(pImpl->curl, CURLOPT_WRITEDATA, &response_body); + + // Set timeout + curl_easy_setopt(pImpl->curl, CURLOPT_TIMEOUT, pImpl->timeout); + curl_easy_setopt(pImpl->curl, CURLOPT_CONNECTTIMEOUT, 10L); + + // Set user agent + curl_easy_setopt(pImpl->curl, CURLOPT_USERAGENT, pImpl->user_agent.c_str()); + + // Set redirect following + if (pImpl->follow_redirects) { + curl_easy_setopt(pImpl->curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(pImpl->curl, CURLOPT_MAXREDIRS, 10L); + } + + // HTTPS support + curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYHOST, 2L); + + // Cookie settings + if (!pImpl->cookie_file.empty()) { + curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, pImpl->cookie_file.c_str()); + curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEJAR, pImpl->cookie_file.c_str()); + } else { + curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, ""); + } + + // Enable automatic decompression + curl_easy_setopt(pImpl->curl, CURLOPT_ACCEPT_ENCODING, ""); + + // Set POST method + curl_easy_setopt(pImpl->curl, CURLOPT_POST, 1L); + + // Set POST data + curl_easy_setopt(pImpl->curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(pImpl->curl, CURLOPT_POSTFIELDSIZE, data.length()); + + // Set Content-Type header + struct curl_slist* headers = nullptr; + std::string content_type_header = "Content-Type: " + content_type; + headers = curl_slist_append(headers, content_type_header.c_str()); + curl_easy_setopt(pImpl->curl, CURLOPT_HTTPHEADER, headers); + + // Perform request + CURLcode res = curl_easy_perform(pImpl->curl); + + // Clean up headers + curl_slist_free_all(headers); + + if (res != CURLE_OK) { + response.error_message = curl_easy_strerror(res); + return response; + } + + // Get response code + long http_code = 0; + curl_easy_getinfo(pImpl->curl, CURLINFO_RESPONSE_CODE, &http_code); + response.status_code = static_cast(http_code); + + // Get Content-Type + char* resp_content_type = nullptr; + curl_easy_getinfo(pImpl->curl, CURLINFO_CONTENT_TYPE, &resp_content_type); + if (resp_content_type) { + response.content_type = resp_content_type; + } + + response.body = std::move(response_body); + + return response; +} + void HttpClient::set_timeout(long timeout_seconds) { pImpl->timeout = timeout_seconds; } diff --git a/src/http_client.h b/src/http_client.h index dcec6fa..e840843 100644 --- a/src/http_client.h +++ b/src/http_client.h @@ -20,6 +20,8 @@ public: ~HttpClient(); HttpResponse fetch(const std::string& url); + HttpResponse post(const std::string& url, const std::string& data, + const std::string& content_type = "application/x-www-form-urlencoded"); void set_timeout(long timeout_seconds); void set_user_agent(const std::string& user_agent); void set_follow_redirects(bool follow); diff --git a/test_post_form.html b/test_post_form.html new file mode 100644 index 0000000..e65b5a1 --- /dev/null +++ b/test_post_form.html @@ -0,0 +1,31 @@ + + + + POST Form Test + + +

Form Method Test

+ +

GET Form

+
+

Name:

+

Email:

+

+
+ +

POST Form

+
+

Username:

+

Password:

+

Message:

+

+
+ +

Form with Special Characters

+
+

Text:

+

Code:

+

+
+ +