root / Assets / Plugins / UnityHTTP / Response.cs @ 9:37719e88568d
History | View | Annotate | Download (8.4 kB)
1 | using System; |
---|---|
2 | using System.Collections; |
3 | using System.Collections.Generic; |
4 | using System.IO; |
5 | using System.Text; |
6 | using System.Globalization; |
7 | using Ionic.Zlib; |
8 | |
9 | namespace HTTP |
10 | { |
11 | public class Response |
12 | { |
13 | public Request request; |
14 | public int status = 200; |
15 | public string message = "OK"; |
16 | public byte[] bytes; |
17 | |
18 | List<byte[]> chunks; |
19 | Dictionary<string, List<string>> headers = new Dictionary<string, List<string>> (); |
20 | |
21 | public string Text { |
22 | get { |
23 | if (bytes == null) |
24 | return ""; |
25 | return System.Text.UTF8Encoding.UTF8.GetString (bytes); |
26 | } |
27 | } |
28 | |
29 | public string Asset { |
30 | get { |
31 | throw new NotSupportedException ("This can't be done, yet."); |
32 | } |
33 | } |
34 | |
35 | public Hashtable Object { |
36 | get { |
37 | if ( bytes == null ) |
38 | { |
39 | return null; |
40 | } |
41 | |
42 | bool result = false; |
43 | Hashtable obj = (Hashtable)JSON.JsonDecode( this.Text, ref result ); |
44 | if ( !result ) |
45 | { |
46 | obj = null; |
47 | } |
48 | |
49 | return obj; |
50 | } |
51 | } |
52 | |
53 | public ArrayList Array { |
54 | get { |
55 | if ( bytes == null ) |
56 | { |
57 | return null; |
58 | } |
59 | |
60 | bool result = false; |
61 | ArrayList array = (ArrayList)JSON.JsonDecode( this.Text, ref result ); |
62 | if ( !result ) |
63 | { |
64 | array = null; |
65 | } |
66 | |
67 | return array; |
68 | } |
69 | } |
70 | |
71 | void AddHeader (string name, string value) |
72 | { |
73 | name = name.ToLower ().Trim (); |
74 | value = value.Trim (); |
75 | if (!headers.ContainsKey (name)) |
76 | headers[name] = new List<string> (); |
77 | headers[name].Add (value); |
78 | } |
79 | |
80 | public List< string > GetHeaders() |
81 | { |
82 | List< string > result = new List< string >(); |
83 | foreach (string name in headers.Keys) { |
84 | foreach (string value in headers[name]) { |
85 | result.Add( name + ": " + value ); |
86 | } |
87 | } |
88 | |
89 | return result; |
90 | } |
91 | |
92 | public List<string> GetHeaders (string name) |
93 | { |
94 | name = name.ToLower ().Trim (); |
95 | if (!headers.ContainsKey (name)) |
96 | return new List<string> (); |
97 | return headers[name]; |
98 | } |
99 | |
100 | public string GetHeader (string name) |
101 | { |
102 | name = name.ToLower ().Trim (); |
103 | if (!headers.ContainsKey (name)) |
104 | return string.Empty; |
105 | return headers[name][headers[name].Count - 1]; |
106 | } |
107 | |
108 | public Response () |
109 | { |
110 | //ReadFromStream (stream); |
111 | } |
112 | |
113 | string ReadLine (Stream stream) |
114 | { |
115 | var line = new List<byte> (); |
116 | while (true) { |
117 | int c = stream.ReadByte (); |
118 | if (c == -1) { |
119 | throw new HTTPException("Unterminated Stream Encountered."); |
120 | } |
121 | if ((byte)c == Request.EOL[1]) |
122 | break; |
123 | line.Add ((byte)c); |
124 | } |
125 | var s = ASCIIEncoding.ASCII.GetString (line.ToArray ()).Trim (); |
126 | return s; |
127 | } |
128 | |
129 | string[] ReadKeyValue (Stream stream) |
130 | { |
131 | string line = ReadLine (stream); |
132 | if (line == "") |
133 | return null; |
134 | else { |
135 | var split = line.IndexOf (':'); |
136 | if (split == -1) |
137 | return null; |
138 | var parts = new string[2]; |
139 | parts[0] = line.Substring (0, split).Trim (); |
140 | parts[1] = line.Substring (split + 1).Trim (); |
141 | return parts; |
142 | } |
143 | |
144 | } |
145 | |
146 | public byte[] TakeChunk() { |
147 | byte[] b = null; |
148 | lock(chunks) { |
149 | if(chunks.Count > 0) { |
150 | b = chunks[0]; |
151 | chunks.RemoveAt(0); |
152 | return b; |
153 | } |
154 | } |
155 | return b; |
156 | } |
157 | |
158 | public void ReadFromStream (Stream inputStream) |
159 | { |
160 | //var inputStream = new BinaryReader(inputStream); |
161 | var top = ReadLine (inputStream).Split (new char[] { ' ' }); |
162 | |
163 | if (!int.TryParse (top[1], out status)) |
164 | throw new HTTPException ("Bad Status Code"); |
165 | |
166 | // MemoryStream is a disposable |
167 | // http://stackoverflow.com/questions/234059/is-a-memory-leak-created-if-a-memorystream-in-net-is-not-closed |
168 | using (var output = new MemoryStream ()) { |
169 | message = string.Join (" ", top, 2, top.Length - 2); |
170 | headers.Clear (); |
171 | |
172 | while (true) { |
173 | // Collect Headers |
174 | string[] parts = ReadKeyValue (inputStream); |
175 | if (parts == null) |
176 | break; |
177 | AddHeader (parts[0], parts[1]); |
178 | } |
179 | |
180 | if ( request.cookieJar != null ) |
181 | { |
182 | List< string > cookies = GetHeaders( "set-cookie" ); |
183 | for ( int cookieIndex = 0; cookieIndex < cookies.Count; ++cookieIndex ) |
184 | { |
185 | string cookieString = cookies[ cookieIndex ]; |
186 | if ( cookieString.IndexOf( "domain=", StringComparison.CurrentCultureIgnoreCase ) == -1 ) |
187 | { |
188 | cookieString += "; domain=" + request.uri.Host; |
189 | } |
190 | |
191 | if ( cookieString.IndexOf( "path=", StringComparison.CurrentCultureIgnoreCase ) == -1 ) |
192 | { |
193 | cookieString += "; path=" + request.uri.AbsolutePath; |
194 | } |
195 | |
196 | request.cookieJar.SetCookie( new Cookie( cookieString ) ); |
197 | } |
198 | } |
199 | |
200 | if (GetHeader ("transfer-encoding") == "chunked") { |
201 | chunks = new List<byte[]> (); |
202 | while (true) { |
203 | // Collect Body |
204 | string hexLength = ReadLine (inputStream); |
205 | //Console.WriteLine("HexLength:" + hexLength); |
206 | if (hexLength == "0") { |
207 | lock(chunks) { |
208 | chunks.Add(new byte[] {}); |
209 | } |
210 | break; |
211 | } |
212 | int length = int.Parse (hexLength, NumberStyles.AllowHexSpecifier); |
213 | for (int i = 0; i < length; i++) |
214 | output.WriteByte ((byte)inputStream.ReadByte ()); |
215 | lock(chunks) { |
216 | if (GetHeader ("content-encoding").Contains ("gzip")) |
217 | chunks.Add (UnZip(output)); |
218 | else |
219 | chunks.Add (output.ToArray ()); |
220 | } |
221 | output.SetLength (0); |
222 | //forget the CRLF. |
223 | inputStream.ReadByte (); |
224 | inputStream.ReadByte (); |
225 | } |
226 | |
227 | while (true) { |
228 | //Collect Trailers |
229 | string[] parts = ReadKeyValue (inputStream); |
230 | if (parts == null) |
231 | break; |
232 | AddHeader (parts[0], parts[1]); |
233 | } |
234 | var unchunked = new List<byte>(); |
235 | foreach(var i in chunks) { |
236 | unchunked.AddRange(i); |
237 | } |
238 | bytes = unchunked.ToArray(); |
239 | |
240 | } else { |
241 | // Read Body |
242 | int contentLength = 0; |
243 | |
244 | try { |
245 | contentLength = int.Parse (GetHeader ("content-length")); |
246 | } catch { |
247 | contentLength = 0; |
248 | } |
249 | |
250 | int _b; |
251 | while( ( contentLength == 0 || output.Length < contentLength ) |
252 | && (_b = inputStream.ReadByte()) != -1 ) { |
253 | output.WriteByte((byte)_b); |
254 | } |
255 | |
256 | if( contentLength > 0 && output.Length != contentLength ) { |
257 | throw new HTTPException ("Response length does not match content length"); |
258 | } |
259 | |
260 | if (GetHeader ("content-encoding").Contains ("gzip")) { |
261 | bytes = UnZip(output); |
262 | } else { |
263 | bytes = output.ToArray (); |
264 | } |
265 | } |
266 | } |
267 | } |
268 | |
269 | |
270 | byte[] UnZip(MemoryStream output) { |
271 | var cms = new MemoryStream (); |
272 | output.Seek (0, SeekOrigin.Begin); |
273 | using (var gz = new GZipStream (output, CompressionMode.Decompress)) { |
274 | var buf = new byte[1024]; |
275 | int byteCount = 0; |
276 | while ((byteCount = gz.Read (buf, 0, buf.Length)) > 0) { |
277 | cms.Write (buf, 0, byteCount); |
278 | } |
279 | } |
280 | return cms.ToArray (); |
281 | } |
282 | |
283 | } |
284 | } |