Line data Source code
1 : #ifndef THINGER_HTTP_CLIENT_FORM_HPP
2 : #define THINGER_HTTP_CLIENT_FORM_HPP
3 :
4 : #include <string>
5 : #include <vector>
6 : #include <filesystem>
7 : #include <optional>
8 :
9 : namespace thinger::http {
10 :
11 : /**
12 : * HTTP form builder for POST requests.
13 : * Supports both URL-encoded and multipart form data.
14 : *
15 : * Usage:
16 : * // Simple form (auto URL-encoded)
17 : * http::form form;
18 : * form.field("username", "john")
19 : * .field("password", "secret");
20 : * auto res = client.post(url, form);
21 : *
22 : * // With files (auto multipart)
23 : * http::form form;
24 : * form.field("name", "John")
25 : * .file("avatar", "/path/to/photo.jpg")
26 : * .file("data", buffer, "data.bin", "application/octet-stream");
27 : * auto res = client.post(url, form);
28 : */
29 : class form {
30 : public:
31 24 : form() = default;
32 :
33 : // ============================================
34 : // Field methods (chainable)
35 : // ============================================
36 :
37 : /**
38 : * Add a text field to the form.
39 : */
40 : form& field(std::string name, std::string value);
41 :
42 : /**
43 : * Add multiple fields at once.
44 : */
45 : form& fields(std::initializer_list<std::pair<std::string, std::string>> pairs);
46 :
47 : // ============================================
48 : // File methods (chainable)
49 : // ============================================
50 :
51 : /**
52 : * Add a file from filesystem path.
53 : * Content-type is auto-detected from extension.
54 : */
55 : form& file(std::string name, const std::filesystem::path& path);
56 :
57 : /**
58 : * Add a file from filesystem path with explicit content type.
59 : */
60 : form& file(std::string name, const std::filesystem::path& path,
61 : std::string content_type);
62 :
63 : /**
64 : * Add a file from memory buffer.
65 : */
66 : form& file(std::string name, std::string content,
67 : std::string filename,
68 : std::string content_type = "application/octet-stream");
69 :
70 : /**
71 : * Add a file from memory buffer (binary data).
72 : */
73 : form& file(std::string name, const void* data, size_t size,
74 : std::string filename,
75 : std::string content_type = "application/octet-stream");
76 :
77 : // ============================================
78 : // Query methods
79 : // ============================================
80 :
81 : /**
82 : * Returns true if form has files (will use multipart encoding).
83 : */
84 34 : bool is_multipart() const { return !files_.empty(); }
85 :
86 : /**
87 : * Returns true if form is empty.
88 : */
89 8 : bool empty() const { return fields_.empty() && files_.empty(); }
90 :
91 : /**
92 : * Get the appropriate Content-Type header value.
93 : */
94 : std::string content_type() const;
95 :
96 : /**
97 : * Build the request body.
98 : */
99 : std::string body() const;
100 :
101 : // ============================================
102 : // Static utilities
103 : // ============================================
104 :
105 : /**
106 : * URL-encode a string.
107 : */
108 : static std::string url_encode(const std::string& str);
109 :
110 : /**
111 : * URL-decode a string.
112 : */
113 : static std::string url_decode(const std::string& str);
114 :
115 : /**
116 : * Guess MIME type from file extension.
117 : */
118 : static std::string mime_type(const std::filesystem::path& path);
119 :
120 : private:
121 : struct file_entry {
122 : std::string name; // Form field name
123 : std::string filename; // Original filename
124 : std::string content_type; // MIME type
125 : std::string content; // File content
126 : };
127 :
128 : std::vector<std::pair<std::string, std::string>> fields_;
129 : std::vector<file_entry> files_;
130 : mutable std::string boundary_; // Generated on first use
131 :
132 : std::string build_urlencoded() const;
133 : std::string build_multipart() const;
134 : const std::string& get_boundary() const;
135 : static std::string generate_boundary();
136 : };
137 :
138 : } // namespace thinger::http
139 :
140 : #endif
|