1 /**
2 	Convenience functions for working with web forms.
3 
4 	Copyright: © 2012-2015 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Jan Krüger
7 */
8 module vibe.http.form;
9 
10 public import vibe.inet.webform;
11 
12 import vibe.http.client : HTTPClientRequest; // for writeFormBody
13 import vibe.http.server;
14 
15 import std.array;
16 import std.conv;
17 import std.range;
18 import std.string;
19 import std.typecons : isTuple;
20 
21 
22 /**
23 	Encodes the given dictionary as URL encoded form data.
24 */
25 void writeFormData(R)(R dst, in string[string] data)
26 	if (isOutputRange!(R, char))
27 {
28 	import vibe.textfilter.urlencode;
29 
30 	bool first = true;
31 	foreach (k, v; data) {
32 		if (first) first = false;
33 		else dst.put("&");
34 		filterURLEncode(dst, k);
35 		dst.put("=");
36 		filterURLEncode(dst, v);
37 	}
38 }
39 
40 ///
41 unittest {
42 	import std.array;
43 	import vibe.core.log;
44 	import vibe.http.form;
45 
46 	void test()
47 	{
48 		auto dst = appender!string();
49 		dst.writeFormData(["field1": "value1", "field2": "value2"]);
50 		logInfo("Form data: %s", dst.data);
51 	}
52 }
53 
54 /**
55 	Encodes the given ranges of `Tuple!(string, string)` as URL encoded form data
56 */
57 void writeFormData(R, PairRange)(R dst, PairRange pr)
58 	if (isOutputRange!(R, char) && isTuple!(ElementType!PairRange) && ElementType!PairRange.length == 2)
59 {
60 	import vibe.textfilter.urlencode;
61 
62    if(pr.empty) return;
63 
64    auto fst = pr.front;
65    pr.popFront();
66 
67    filterURLEncode(dst, fst[0]);
68    dst.put("=");
69    filterURLEncode(dst, fst[1]);
70 
71 	foreach (pair; pr) {
72 		dst.put("&");
73 		filterURLEncode(dst, pair[0]);
74 		dst.put("=");
75 		filterURLEncode(dst, pair[1]);
76 	}
77 }
78 
79 /**
80 	Writes a `vibe.http.client.HTTPClientRequest` body as URL encoded form data.
81 */
82 void writeFormBody(HTTPClientRequest req, in string[string] form)
83 {
84 	import vibe.http.form;
85 	import vibe.stream.wrapper;
86 
87 	StringLengthCountingRange len;
88 	writeFormData(&len, form);
89 	req.contentType = "application/x-www-form-urlencoded";
90 	req.contentLength = len.count;
91 	auto rng = streamOutputRange(req.bodyWriter);
92 	writeFormData(&rng, form);
93 }
94 
95 ///
96 unittest {
97 	import vibe.core.log;
98 	import vibe.http.client;
99 	import vibe.http.form;
100 	import vibe.stream.operations;
101 
102 	void sendForm()
103 	{
104 		requestHTTP("http://example.com/form",
105 			(scope req) {
106 				req.method = HTTPMethod.POST;
107 				req.writeFormBody(["field1": "value1", "field2": "value2"]);
108 			},
109 			(scope res) {
110 				logInfo("Response: %s", res.bodyReader.readAllUTF8());
111 			});
112 	}
113 }
114 
115 /**
116 	Writes a `vibe.http.client.HTTPClientRequest` body as URL encoded form data.
117 
118 	Params:
119 	  req  = Request object to write to.
120 	  form = range of `t = Tuple!(string, string)`,
121 			 where `t[0]` is the name and `t[1]` the
122 			 value of a form entry.
123 */
124 void writeFormBody(PairRange)(HTTPClientRequest req, PairRange form)
125    if(isTuple!(ElementType!PairRange) && ElementType!PairRange.length == 2)
126 {
127 	import vibe.http.form;
128 	import vibe.stream.wrapper;
129 
130 	StringLengthCountingRange len;
131 	writeFormData(&len, form.save);
132 	req.contentType = "application/x-www-form-urlencoded";
133 	req.contentLength = len.count;
134 	auto rng = streamOutputRange(req.bodyWriter);
135 	writeFormData(&rng, form);
136 }
137 
138 ///
139 unittest {
140 	import vibe.core.log;
141 	import vibe.http.client;
142 	import vibe.http.form;
143 	import vibe.stream.operations;
144 	import std.range;
145 
146 	void sendForm()
147 	{
148 		string[] names = ["foo", "bar", "baz"];
149 		string[] values = ["1", "2", "3"];
150 		auto form = zip(names, values);
151 		requestHTTP("http://example.com/form",
152 			(scope req) {
153 				req.method = HTTPMethod.POST;
154 				req.writeFormBody(form);
155 			},
156 			(scope res) {
157 				logInfo("Response: %s", res.bodyReader.readAllUTF8());
158 			});
159 	}
160 }
161 
162 
163 /// private
164 struct StringLengthCountingRange {
165 	import std.utf;
166 	size_t count = 0;
167 	void put(string str) { count += str.length; }
168 	void put(dchar ch) { count += codeLength!char(ch); }
169 }