Implementation:ClickHouse ClickHouse Poco HTMLForm
| Source File | base/poco/Net/src/HTMLForm.cpp |
|---|---|
| Lines of Code | 467 |
| Principle | Principle:ClickHouse_ClickHouse_HTTP_Client_Communication |
| Domain | Networking, HTTP |
| Language | C++ |
| Last Updated | 2026-02-08 00:00 GMT |
Overview
Implementation of HTML form handling for HTTP requests, supporting both URL-encoded and multipart/form-data encodings with file upload capabilities.
Purpose
Provides comprehensive HTML form parsing and generation for HTTP communication, handling form data extraction from requests and formatting for transmission.
Key Classes
- HTMLForm: Main form container extending `NameValueCollection`
- HTMLFormCountingOutputStream: Utility for calculating content length
- Part: Internal structure for multipart attachments
Core Functionality
Form Initialization
Multiple construction modes:
// Empty form
HTMLForm();
HTMLForm(const std::string& encoding);
// Parse from HTTP request
HTMLForm(const HTTPRequest& request);
HTMLForm(const HTTPRequest& request, std::istream& requestBody);
HTMLForm(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler);
Loading Forms
Extract form data from HTTP requests:
void load(const HTTPRequest& request);
void load(const HTTPRequest& request, std::istream& requestBody);
void load(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler);
// Parse query string
void read(const std::string& queryString);
// Read from stream
void read(std::istream& istr);
void read(std::istream& istr, PartHandler& handler);
Handles:
- Query parameters from URI
- POST/PUT body in URL-encoded format
- Multipart/form-data with file uploads
Writing Forms
Generate form data for transmission:
void prepareSubmit(HTTPRequest& request, int options = 0);
void write(std::ostream& ostr);
void write(std::ostream& ostr, const std::string& boundary);
std::streamsize calculateContentLength();
Options:
- `OPT_USE_CONTENT_LENGTH`: Force Content-Length header (vs chunked)
Encoding Support
Two primary encodings:
static const std::string ENCODING_URL; // "application/x-www-form-urlencoded"
static const std::string ENCODING_MULTIPART; // "multipart/form-data"
void setEncoding(const std::string& encoding);
const std::string& getEncoding() const;
Multipart Support
Handle file uploads and complex data:
struct Part {
std::string name;
PartSource* pSource;
};
void addPart(const std::string& name, PartSource* pSource);
`PartSource` provides:
- Stream interface to file/data
- Content type
- Filename
- Content length
Security Limits
Protect against malicious inputs:
void setFieldLimit(int limit); // Maximum number of fields
void setValueLengthLimit(int limit); // Maximum field value length
static const int MAX_NAME_LENGTH = 1024;
static const int DFL_FIELD_LIMIT = 100;
static const int DFL_MAX_VALUE_LENGTH = 256 * 1024;
Implementation Details
URL-Encoded Parsing
Parse `application/x-www-form-urlencoded` data:
void readUrl(std::istream& istr) {
// Read name=value&name=value format
// '+' → space
// URL decode names and values
// Remove UTF-8 BOM from first field
// Enforce field and value limits
}
Format: `field1=value1&field2=value2`
- Plus signs converted to spaces
- Percent-encoding decoded
- UTF-8 BOM stripped from first field name
Multipart Parsing
Parse `multipart/form-data`:
void readMultipart(std::istream& istr, PartHandler& handler) {
MultipartReader reader(istr, _boundary);
while (reader.hasNextPart()) {
MessageHeader header;
reader.nextPart(header);
// Extract Content-Disposition parameters
if (has filename) {
handler.handlePart(header, reader.stream());
} else {
// Read field value
}
}
}
Delegates file uploads to `PartHandler`:
- `NullPartHandler`: Ignores file parts
- Custom handlers can save to disk, database, etc.
URL-Encoded Writing
Generate URL-encoded output:
void writeUrl(std::ostream& ostr) {
// Format: name1=value1&name2=value2
// Encode special characters: !?#/'\",;:$&()[]*+=@
for (auto& field : fields) {
URI::encode(name, reserved, encoded_name);
URI::encode(value, reserved, encoded_value);
ostr << encoded_name << "=" << encoded_value;
if (not_last) ostr << "&";
}
}
Multipart Writing
Generate multipart/form-data:
void writeMultipart(std::ostream& ostr) {
MultipartWriter writer(ostr, _boundary);
// Write simple fields
for (auto& field : fields) {
MessageHeader header;
header.set("Content-Disposition",
"form-data; name=\"" + name + "\"");
writer.nextPart(header);
ostr << value;
}
// Write file parts
for (auto& part : _parts) {
MessageHeader header(part.pSource->headers());
header.set("Content-Disposition",
"form-data; name=\"" + name +
"\"; filename=\"" + filename + "\"");
header.set("Content-Type", part.pSource->mediaType());
writer.nextPart(header);
StreamCopier::copyStream(part.pSource->stream(), ostr);
}
writer.close();
}
Content Length Calculation
For non-chunked transmission:
std::streamsize calculateContentLength() {
HTMLFormCountingOutputStream counter;
write(counter);
if (counter.isValid())
return counter.chars();
else
return UNKNOWN_CONTENT_LENGTH;
}
`HTMLFormCountingOutputStream` counts bytes without writing, but sets invalid flag if any `PartSource` has unknown length.
Usage Examples
Parsing Form Data
// From HTTP request
HTTPRequest request;
std::istream& body = session.receiveRequest(request);
HTMLForm form(request, body);
// Access fields
std::string username = form.get("username");
std::string password = form.get("password");
Creating Form Submission
// URL-encoded form
HTMLForm form(HTMLForm::ENCODING_URL);
form.set("user", "alice");
form.set("pass", "secret");
HTTPRequest request(HTTPRequest::HTTP_POST, "/login");
form.prepareSubmit(request);
std::ostream& ostr = session.sendRequest(request);
form.write(ostr);
File Upload
// Multipart form with file
HTMLForm form(HTMLForm::ENCODING_MULTIPART);
form.set("description", "My file");
form.addPart("file", new FilePartSource("/path/to/file.pdf"));
HTTPRequest request(HTTPRequest::HTTP_POST, "/upload");
form.prepareSubmit(request);
std::ostream& ostr = session.sendRequest(request);
form.write(ostr);
Handling File Uploads (Server)
class MyPartHandler : public PartHandler {
public:
void handlePart(const MessageHeader& header, std::istream& stream) override {
// Extract filename from Content-Disposition
// Save stream to file
}
};
MyPartHandler handler;
HTMLForm form(request, body, handler);
Dependencies
- `Poco::Net::HTTPRequest`: HTTP request representation
- `Poco::Net::MultipartReader`: Multipart parsing
- `Poco::Net::MultipartWriter`: Multipart generation
- `Poco::Net::PartSource`: File/data source abstraction
- `Poco::Net::PartHandler`: File upload callback
- `Poco::URI`: URL encoding/decoding
- `Poco::StreamCopier`: Stream copying utilities
Security Considerations
- Field count limits prevent DoS attacks
- Value length limits prevent memory exhaustion
- Input validation prevents injection attacks
- Proper encoding prevents XSS vulnerabilities
- UTF-8 BOM handling prevents parser confusion
Testing Considerations
- Test both URL-encoded and multipart forms
- Verify proper URL encoding/decoding
- Test file upload/download
- Check limit enforcement
- Test with empty values and special characters
- Verify UTF-8 and international characters
- Test boundary collision handling in multipart