[System] Removal of the NET_2_0 in the source code
[mono.git] / mcs / class / System / System.Net / HttpListenerResponse.cs
1 //
2 // System.Net.HttpListenerResponse
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //
7 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 #if SECURITY_DEP
30
31 using System.Globalization;
32 using System.IO;
33 using System.Text;
34 namespace System.Net {
35         public sealed class HttpListenerResponse : IDisposable
36         {
37                 bool disposed;
38                 Encoding content_encoding;
39                 long content_length;
40                 bool cl_set;
41                 string content_type;
42                 CookieCollection cookies;
43                 WebHeaderCollection headers = new WebHeaderCollection ();
44                 bool keep_alive = true;
45                 ResponseStream output_stream;
46                 Version version = HttpVersion.Version11;
47                 string location;
48                 int status_code = 200;
49                 string status_description = "OK";
50                 bool chunked;
51                 HttpListenerContext context;
52                 internal bool HeadersSent;
53                 bool force_close_chunked;
54
55                 internal HttpListenerResponse (HttpListenerContext context)
56                 {
57                         this.context = context;
58                 }
59
60                 internal bool ForceCloseChunked {
61                         get { return force_close_chunked; }
62                 }
63
64                 public Encoding ContentEncoding {
65                         get {
66                                 if (content_encoding == null)
67                                         content_encoding = Encoding.Default;
68                                 return content_encoding;
69                         }
70                         set {
71                                 if (disposed)
72                                         throw new ObjectDisposedException (GetType ().ToString ());
73
74                                 //TODO: is null ok?
75                                 if (HeadersSent)
76                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
77                                         
78                                 content_encoding = value;
79                         }
80                 }
81
82                 public long ContentLength64 {
83                         get { return content_length; }
84                         set {
85                                 if (disposed)
86                                         throw new ObjectDisposedException (GetType ().ToString ());
87
88                                 if (HeadersSent)
89                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
90
91                                 if (value < 0)
92                                         throw new ArgumentOutOfRangeException ("Must be >= 0", "value");
93
94                                 cl_set = true;
95                                 content_length = value;
96                         }
97                 }
98                 
99                 public string ContentType {
100                         get { return content_type; }
101                         set {
102                                 // TODO: is null ok?
103                                 if (disposed)
104                                         throw new ObjectDisposedException (GetType ().ToString ());
105
106                                 if (HeadersSent)
107                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
108
109                                 content_type = value;
110                         }
111                 }
112
113                 // RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
114                 public CookieCollection Cookies {
115                         get {
116                                 if (cookies == null)
117                                         cookies = new CookieCollection ();
118                                 return cookies;
119                         }
120                         set { cookies = value; } // null allowed?
121                 }
122
123                 public WebHeaderCollection Headers {
124                         get { return headers; }
125                         set {
126                 /**
127                  *      "If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
128                  *      WWW-Authenticate header using the Headers property, an exception will be
129                  *      thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
130                  *      You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
131                 */
132                 // TODO: check if this is marked readonly after headers are sent.
133                                 headers = value;
134                         }
135                 }
136
137                 public bool KeepAlive {
138                         get { return keep_alive; }
139                         set {
140                                 if (disposed)
141                                         throw new ObjectDisposedException (GetType ().ToString ());
142
143                                 if (HeadersSent)
144                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
145                                         
146                                 keep_alive = value;
147                         }
148                 }
149
150                 public Stream OutputStream {
151                         get {
152                                 if (output_stream == null)
153                                         output_stream = context.Connection.GetResponseStream ();
154                                 return output_stream;
155                         }
156                 }
157                 
158                 public Version ProtocolVersion {
159                         get { return version; }
160                         set {
161                                 if (disposed)
162                                         throw new ObjectDisposedException (GetType ().ToString ());
163
164                                 if (HeadersSent)
165                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
166                                         
167                                 if (value == null)
168                                         throw new ArgumentNullException ("value");
169
170                                 if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
171                                         throw new ArgumentException ("Must be 1.0 or 1.1", "value");
172
173                                 if (disposed)
174                                         throw new ObjectDisposedException (GetType ().ToString ());
175
176                                 version = value;
177                         }
178                 }
179
180                 public string RedirectLocation {
181                         get { return location; }
182                         set {
183                                 if (disposed)
184                                         throw new ObjectDisposedException (GetType ().ToString ());
185
186                                 if (HeadersSent)
187                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
188                                         
189                                 location = value;
190                         }
191                 }
192
193                 public bool SendChunked {
194                         get { return chunked; }
195                         set {
196                                 if (disposed)
197                                         throw new ObjectDisposedException (GetType ().ToString ());
198
199                                 if (HeadersSent)
200                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
201                                         
202                                 chunked = value;
203                         }
204                 }
205
206                 public int StatusCode {
207                         get { return status_code; }
208                         set {
209                                 if (disposed)
210                                         throw new ObjectDisposedException (GetType ().ToString ());
211
212                                 if (HeadersSent)
213                                         throw new InvalidOperationException ("Cannot be changed after headers are sent.");
214                                         
215                                 if (value < 100 || value > 999)
216                                         throw new ProtocolViolationException ("StatusCode must be between 100 and 999.");
217                                 status_code = value;
218                                 status_description = GetStatusDescription (value);
219                         }
220                 }
221
222                 internal static string GetStatusDescription (int code)
223                 {
224                         switch (code){
225                         case 100: return "Continue";
226                         case 101: return "Switching Protocols";
227                         case 102: return "Processing";
228                         case 200: return "OK";
229                         case 201: return "Created";
230                         case 202: return "Accepted";
231                         case 203: return "Non-Authoritative Information";
232                         case 204: return "No Content";
233                         case 205: return "Reset Content";
234                         case 206: return "Partial Content";
235                         case 207: return "Multi-Status";
236                         case 300: return "Multiple Choices";
237                         case 301: return "Moved Permanently";
238                         case 302: return "Found";
239                         case 303: return "See Other";
240                         case 304: return "Not Modified";
241                         case 305: return "Use Proxy";
242                         case 307: return "Temporary Redirect";
243                         case 400: return "Bad Request";
244                         case 401: return "Unauthorized";
245                         case 402: return "Payment Required";
246                         case 403: return "Forbidden";
247                         case 404: return "Not Found";
248                         case 405: return "Method Not Allowed";
249                         case 406: return "Not Acceptable";
250                         case 407: return "Proxy Authentication Required";
251                         case 408: return "Request Timeout";
252                         case 409: return "Conflict";
253                         case 410: return "Gone";
254                         case 411: return "Length Required";
255                         case 412: return "Precondition Failed";
256                         case 413: return "Request Entity Too Large";
257                         case 414: return "Request-Uri Too Long";
258                         case 415: return "Unsupported Media Type";
259                         case 416: return "Requested Range Not Satisfiable";
260                         case 417: return "Expectation Failed";
261                         case 422: return "Unprocessable Entity";
262                         case 423: return "Locked";
263                         case 424: return "Failed Dependency";
264                         case 500: return "Internal Server Error";
265                         case 501: return "Not Implemented";
266                         case 502: return "Bad Gateway";
267                         case 503: return "Service Unavailable";
268                         case 504: return "Gateway Timeout";
269                         case 505: return "Http Version Not Supported";
270                         case 507: return "Insufficient Storage";
271                         }
272                         return "";
273                 }
274
275                 public string StatusDescription {
276                         get { return status_description; }
277                         set {
278                                 status_description = value;
279                         }
280                 }
281
282                 void IDisposable.Dispose ()
283                 {
284                         Close (true); //TODO: Abort or Close?
285                 }
286
287                 public void Abort ()
288                 {
289                         if (disposed)
290                                 return;
291
292                         Close (true);
293                 }
294
295                 public void AddHeader (string name, string value)
296                 {
297                         if (name == null)
298                                 throw new ArgumentNullException ("name");
299
300                         if (name == "")
301                                 throw new ArgumentException ("'name' cannot be empty", "name");
302                         
303                         //TODO: check for forbidden headers and invalid characters
304                         if (value.Length > 65535)
305                                 throw new ArgumentOutOfRangeException ("value");
306
307                         headers.Set (name, value);
308                 }
309
310                 public void AppendCookie (Cookie cookie)
311                 {
312                         if (cookie == null)
313                                 throw new ArgumentNullException ("cookie");
314                         
315                         Cookies.Add (cookie);
316                 }
317
318                 public void AppendHeader (string name, string value)
319                 {
320                         if (name == null)
321                                 throw new ArgumentNullException ("name");
322
323                         if (name == "")
324                                 throw new ArgumentException ("'name' cannot be empty", "name");
325                         
326                         if (value.Length > 65535)
327                                 throw new ArgumentOutOfRangeException ("value");
328
329                         headers.Add (name, value);
330                 }
331
332                 void Close (bool force)
333                 {
334                         disposed = true;
335                         context.Connection.Close (force);
336                 }
337
338                 public void Close ()
339                 {
340                         if (disposed)
341                                 return;
342
343                         Close (false);
344                 }
345
346                 public void Close (byte [] responseEntity, bool willBlock)
347                 {
348                         if (disposed)
349                                 return;
350
351                         if (responseEntity == null)
352                                 throw new ArgumentNullException ("responseEntity");
353
354                         //TODO: if willBlock -> BeginWrite + Close ?
355                         ContentLength64 = responseEntity.Length;
356                         OutputStream.Write (responseEntity, 0, (int) content_length);
357                         Close (false);
358                 }
359
360                 public void CopyFrom (HttpListenerResponse templateResponse)
361                 {
362                         headers.Clear ();
363                         headers.Add (templateResponse.headers);
364                         content_length = templateResponse.content_length;
365                         status_code = templateResponse.status_code;
366                         status_description = templateResponse.status_description;
367                         keep_alive = templateResponse.keep_alive;
368                         version = templateResponse.version;
369                 }
370
371                 public void Redirect (string url)
372                 {
373                         StatusCode = 302; // Found
374                         location = url;
375                 }
376
377                 bool FindCookie (Cookie cookie)
378                 {
379                         string name = cookie.Name;
380                         string domain = cookie.Domain;
381                         string path = cookie.Path;
382                         foreach (Cookie c in cookies) {
383                                 if (name != c.Name)
384                                         continue;
385                                 if (domain != c.Domain)
386                                         continue;
387                                 if (path == c.Path)
388                                         return true;
389                         }
390
391                         return false;
392                 }
393
394                 internal void SendHeaders (bool closing, MemoryStream ms)
395                 {
396                         Encoding encoding = content_encoding;
397                         if (encoding == null)
398                                 encoding = Encoding.Default;
399
400                         if (content_type != null) {
401                                 if (content_encoding != null && content_type.IndexOf ("charset=", StringComparison.Ordinal) == -1) {
402                                         string enc_name = content_encoding.WebName;
403                                         headers.SetInternal ("Content-Type", content_type + "; charset=" + enc_name);
404                                 } else {
405                                         headers.SetInternal ("Content-Type", content_type);
406                                 }
407                         }
408
409                         if (headers ["Server"] == null)
410                                 headers.SetInternal ("Server", "Mono-HTTPAPI/1.0");
411
412                         CultureInfo inv = CultureInfo.InvariantCulture;
413                         if (headers ["Date"] == null)
414                                 headers.SetInternal ("Date", DateTime.UtcNow.ToString ("r", inv));
415
416                         if (!chunked) {
417                                 if (!cl_set && closing) {
418                                         cl_set = true;
419                                         content_length = 0;
420                                 }
421
422                                 if (cl_set)
423                                         headers.SetInternal ("Content-Length", content_length.ToString (inv));
424                         }
425
426                         Version v = context.Request.ProtocolVersion;
427                         if (!cl_set && !chunked && v >= HttpVersion.Version11)
428                                 chunked = true;
429                                 
430                         /* Apache forces closing the connection for these status codes:
431                          *      HttpStatusCode.BadRequest               400
432                          *      HttpStatusCode.RequestTimeout           408
433                          *      HttpStatusCode.LengthRequired           411
434                          *      HttpStatusCode.RequestEntityTooLarge    413
435                          *      HttpStatusCode.RequestUriTooLong        414
436                          *      HttpStatusCode.InternalServerError      500
437                          *      HttpStatusCode.ServiceUnavailable       503
438                          */
439                         bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
440                                         status_code == 413 || status_code == 414 || status_code == 500 ||
441                                         status_code == 503);
442
443                         if (conn_close == false)
444                                 conn_close = !context.Request.KeepAlive;
445
446                         // They sent both KeepAlive: true and Connection: close!?
447                         if (!keep_alive || conn_close) {
448                                 headers.SetInternal ("Connection", "close");
449                                 conn_close = true;
450                         }
451
452                         if (chunked)
453                                 headers.SetInternal ("Transfer-Encoding", "chunked");
454
455                         int reuses = context.Connection.Reuses;
456                         if (reuses >= 100) {
457                                 force_close_chunked = true;
458                                 if (!conn_close) {
459                                         headers.SetInternal ("Connection", "close");
460                                         conn_close = true;
461                                 }
462                         }
463
464                         if (!conn_close) {
465                                 headers.SetInternal ("Keep-Alive", String.Format ("timeout=15,max={0}", 100 - reuses));
466                                 if (context.Request.ProtocolVersion <= HttpVersion.Version10)
467                                         headers.SetInternal ("Connection", "keep-alive");
468                         }
469
470                         if (location != null)
471                                 headers.SetInternal ("Location", location);
472
473                         if (cookies != null) {
474                                 foreach (Cookie cookie in cookies)
475                                         headers.SetInternal ("Set-Cookie", cookie.ToClientString ());
476                         }
477
478                         StreamWriter writer = new StreamWriter (ms, encoding, 256);
479                         writer.Write ("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
480                         string headers_str = headers.ToStringMultiValue ();
481                         writer.Write (headers_str);
482                         writer.Flush ();
483                         int preamble = (encoding.CodePage == 65001) ? 3 : encoding.GetPreamble ().Length;
484                         if (output_stream == null)
485                                 output_stream = context.Connection.GetResponseStream ();
486
487                         /* Assumes that the ms was at position 0 */
488                         ms.Position = preamble;
489                         HeadersSent = true;
490                 }
491
492                 public void SetCookie (Cookie cookie)
493                 {
494                         if (cookie == null)
495                                 throw new ArgumentNullException ("cookie");
496
497                         if (cookies != null) {
498                                 if (FindCookie (cookie))
499                                         throw new ArgumentException ("The cookie already exists.");
500                         } else {
501                                 cookies = new CookieCollection ();
502                         }
503
504                         cookies.Add (cookie);
505                 }
506         }
507 }
508 #endif
509