1 module hunt.http.client.FormBody;
2 
3 import hunt.http.HttpBody;
4 import hunt.http.HttpOutputStream;
5 
6 import hunt.io.ByteBuffer;
7 import hunt.io.HeapByteBuffer;
8 import hunt.io.BufferUtils;
9 import hunt.Exceptions;
10 import hunt.logging;
11 import hunt.net.util.UrlEncoded;
12 import hunt.util.MimeType;
13 
14 import std.array;
15 import std.conv;
16 import std.container;
17 
18 
19 /** 
20  * 
21  */
22 final class FormBody : HttpBody {
23 	enum CONTENT_TYPE = MimeType.APPLICATION_X_WWW_FORM_VALUE;
24 	private ByteBuffer _buffer;
25 
26 	private string[] _encodedNames;
27 	private string[] _encodedValues;
28 
29 	this(string[] encodedNames, string[] encodedValues) {
30 		this._encodedNames = encodedNames;
31 		this._encodedValues = encodedValues;
32 	}
33 
34 	/** The number of key-value pairs in this form-encoded body. */
35 	int size() {
36 		return cast(int) _encodedNames.length;
37 	}
38 
39 	string encodedName(int index) {
40 		return _encodedNames[index];
41 	}
42 
43 	string name(int index) {
44 		return UrlEncoded.decodeString(_encodedNames[index]);
45 	}
46 
47 	string encodedValue(int index) {
48 		return _encodedValues[index];
49 	}
50 
51 	string value(int index) {
52 		return UrlEncoded.decodeString(_encodedValues[index]);
53 	}
54 
55 	override string contentType() {
56 		return CONTENT_TYPE;
57 	}
58 
59 	override long contentLength() {
60         if(_buffer is null) {
61             _buffer = encode();
62         }
63 
64 		return _buffer.remaining();
65 	}
66 
67 	override void writeTo(HttpOutputStream sink) {
68         if(_buffer is null) { 
69            	_buffer = encode();
70         }
71 
72 		sink.write(_buffer);
73 	}
74 
75 	/**
76    * Either writes this request to {@code sink} or measures its content length. We have one method
77    * do double-duty to make sure the counting and content are consistent, particularly when it comes
78    * to awkward operations like measuring the encoded length of header strings, or the
79    * length-in-digits of an encoded integer.
80    */
81 	private ByteBuffer encode() {
82 		ByteBuffer buffer = BufferUtils.allocate(2 * 1024);
83 		for (size_t i = 0; i < _encodedNames.length; i++) {
84 			if (i > 0)
85 				buffer.put('&');
86 			buffer.put(cast(byte[])_encodedNames[i]);
87 			buffer.put('=');
88 			buffer.put(cast(byte[])_encodedValues[i]);
89 		}
90 
91 		buffer.flip();
92 
93 		version(HUNT_HTTP_DEBUG) trace(cast(string)buffer.peekRemaining());
94 
95 		return buffer;
96 	}
97 
98 	static final class Builder {
99 		private Array!string names;
100 		private Array!string values;
101 		// private final Charset charset;
102 
103 		this() {
104 			// this(null);
105 		}
106 
107 		// this(Charset charset) {
108 		//   this.charset = charset;
109 		// }
110 
111 		Builder add(string name, string value) {
112 			if (name.empty)
113 				throw new NullPointerException("name is empty");
114 			if (value.empty)
115 				throw new NullPointerException("value is empty");
116 
117 			names.insertBack(UrlEncoded.encodeString(name));
118 			values.insertBack(UrlEncoded.encodeString(value));
119 
120 			return this;
121 		}
122 
123 		Builder add(T)(string name, T value) if(!is(T == string)) {
124 			return add(name, to!string(value));
125 		}
126 
127 		Builder addEncoded(string name, string value) {
128 			if (name.empty)
129 				throw new NullPointerException("name is empty");
130 			if (value.empty)
131 				throw new NullPointerException("value is empty");
132 
133 			names.insertBack(name);
134 			values.insertBack(value);
135 			return this;
136 		}
137 
138 		FormBody build() {
139 			return new FormBody(names.array, values.array);
140 		}
141 	}
142 }
143 
144 alias FormBodyBuilder = FormBody.Builder;