mirror of
https://github.com/m1ngsama/TUT.git
synced 2025-12-26 12:04:11 +00:00
feat: Implement POST form submission
Add full support for POST method form submissions alongside existing GET support. Changes: - Add HttpClient::post() method with configurable Content-Type - Implement URL encoding for form data (RFC 3986 compliant) - Update Browser::submit_form() to detect and handle both GET and POST methods - Add proper form data encoding with special character handling - Include Content-Type header for POST requests Features: - Automatic method detection from form's method attribute - URL-encoded form data (application/x-www-form-urlencoded) - Proper encoding of special characters (spaces, &, =, etc.) - Status messages for form submission feedback - History tracking for POST responses Testing: - Add test_post_form.html with GET, POST, and special character test cases - Uses httpbin.org endpoints for validation Resolves TODO at browser.cpp:238 - POST handling is now fully implemented.
This commit is contained in:
parent
0ecedb1aed
commit
dec72c678f
4 changed files with 194 additions and 14 deletions
|
|
@ -5,6 +5,8 @@
|
|||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
|
||||
class Browser::Impl {
|
||||
public:
|
||||
|
|
@ -207,9 +209,27 @@ 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;
|
||||
|
||||
|
|
@ -218,8 +238,8 @@ public:
|
|||
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;
|
||||
|
|
@ -227,22 +247,60 @@ public:
|
|||
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<int>(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() {
|
||||
|
|
|
|||
|
|
@ -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<int>(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
31
test_post_form.html
Normal file
31
test_post_form.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>POST Form Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Form Method Test</h1>
|
||||
|
||||
<h2>GET Form</h2>
|
||||
<form action="https://httpbin.org/get" method="get">
|
||||
<p>Name: <input type="text" name="name" value="John"></p>
|
||||
<p>Email: <input type="text" name="email" value="john@example.com"></p>
|
||||
<p><input type="submit" value="Submit GET"></p>
|
||||
</form>
|
||||
|
||||
<h2>POST Form</h2>
|
||||
<form action="https://httpbin.org/post" method="post">
|
||||
<p>Username: <input type="text" name="username" value="testuser"></p>
|
||||
<p>Password: <input type="password" name="password" value="secret123"></p>
|
||||
<p>Message: <input type="text" name="message" value="Hello World"></p>
|
||||
<p><input type="submit" value="Submit POST"></p>
|
||||
</form>
|
||||
|
||||
<h2>Form with Special Characters</h2>
|
||||
<form action="https://httpbin.org/post" method="post">
|
||||
<p>Text: <input type="text" name="text" value="Hello & goodbye!"></p>
|
||||
<p>Code: <input type="text" name="code" value="a=b&c=d"></p>
|
||||
<p><input type="submit" value="Submit"></p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue