PageRenderTime 87ms CodeModel.GetById 51ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 1ms

/src/Manos/Manos.Http/HttpHeaders.cs

http://github.com/jacksonh/manos
C# | 330 lines | 239 code | 62 blank | 29 comment | 71 complexity | db86b9e3a057ee0d7fc2dd400ef0e087 MD5 | raw file
  1//
  2// Copyright (C) 2010 Jackson Harper (jackson@manosdemono.com)
  3//
  4// Permission is hereby granted, free of charge, to any person obtaining
  5// a copy of this software and associated documentation files (the
  6// "Software"), to deal in the Software without restriction, including
  7// without limitation the rights to use, copy, modify, merge, publish,
  8// distribute, sublicense, and/or sell copies of the Software, and to
  9// permit persons to whom the Software is furnished to do so, subject to
 10// the following conditions:
 11// 
 12// The above copyright notice and this permission notice shall be
 13// included in all copies or substantial portions of the Software.
 14// 
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 16// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 17// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 18// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 19// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 20// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 21// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 22//
 23//
 24
 25
 26using System;
 27using System.IO;
 28using System.Text;
 29using System.Net;
 30using System.Net.Sockets;
 31using System.Globalization;
 32using System.Collections;
 33using System.Collections.Generic;
 34using System.Collections.Specialized;
 35
 36
 37
 38namespace Manos.Http {
 39
 40	public class HttpHeaders {
 41
 42		public static readonly string CONTENT_LENGTH_KEY = "Content-Length";
 43		
 44		private long? content_length;
 45		private Encoding encoding;
 46
 47		Dictionary<string,string> items = new Dictionary<string,string> ();
 48
 49		public HttpHeaders ()
 50		{
 51		}
 52
 53		public long? ContentLength {
 54			get { return content_length; }
 55			set {
 56				if (value < 0)
 57					throw new ArgumentException ("value");
 58				
 59				content_length = value;
 60				if (value == null) {
 61					items.Remove (CONTENT_LENGTH_KEY);
 62					return;
 63				}
 64				items [CONTENT_LENGTH_KEY] = value.ToString ();
 65			}
 66		}
 67
 68		public Encoding ContentEncoding {
 69			get {
 70				if (encoding == null)
 71					SetEncodingInternal ();
 72				return encoding;
 73			}
 74			set {
 75				encoding = value;
 76			}
 77		}
 78
 79		private void SetEncodingInternal ()
 80		{
 81			string content;
 82
 83			if (!TryGetNormalizedValue ("Content-Type", out content)) {
 84				encoding = Encoding.ASCII;
 85				return;
 86			}
 87
 88			string charset = HttpHeaders.GetAttribute (content, "; charset=");
 89			if (charset == null) {
 90				encoding = Encoding.Default;
 91				return;
 92			}
 93
 94			try {
 95				encoding = Encoding.GetEncoding (charset);
 96			} catch (Exception e) {
 97				Console.Error.WriteLine ("[non-fatal] Exception while setting encoding:");
 98				Console.Error.WriteLine (e);
 99
100				encoding = Encoding.Default;
101			}
102		}
103
104		public Dictionary<string,string>.KeyCollection Keys {
105			get { return items.Keys; }
106		}
107
108		public string this [string name] {
109			get {
110				if (name == null)
111					throw new ArgumentNullException ("name");
112				return items [NormalizeName (name)];
113			}
114		}
115
116		public int Count {
117			get { return items.Count; }	
118		}
119
120		public bool TryGetValue (string key, out string value)
121		{
122			return TryGetNormalizedValue (NormalizeName (key), out value);
123		}
124
125		public bool TryGetNormalizedValue (string key, out string value)
126		{
127			return items.TryGetValue (key, out value);
128		}
129
130		public void Parse (TextReader reader)
131		{
132			string line = reader.ReadLine ();
133			while (line != null) {
134				int line_end = line.Length - 1;
135				
136				if (String.IsNullOrEmpty (line))
137					return;
138				
139				if (Char.IsWhiteSpace (line [0]))
140					throw new HttpException ("Malformed HTTP header. Found whitespace before data.");
141				
142				while (Char.IsWhiteSpace (line [line_end])) {
143					line_end--;
144					if (line_end == 0)
145						throw new HttpException ("Malformed HTTP header. No data found.");
146				}
147
148				int colon = line.IndexOf (':');
149				if (colon <= 0) 
150					throw new HttpException ("Malformed HTTP header. No colon found.");
151				if (colon >= line_end)
152					throw new HttpException ("Malformed HTTP header. No value found.");
153				string value = line.Substring (colon + 1, line_end - colon).TrimStart ();
154				if (value.Length == 0)
155					throw new HttpException ("Malformed HTTP header. No Value found.");
156				
157				string key = line.Substring (0, colon);
158				
159				//
160				// If the next line starts with whitespace its part of the current value
161				//
162				line = reader.ReadLine ();
163				while (line != null && line.Length > 0 && Char.IsWhiteSpace (line [0])) {
164					value += " " + line.Trim ();
165					line = reader.ReadLine ();
166				}
167
168				SetHeader (key, value);
169			}
170		}
171
172		public void Write (StringBuilder builder, ICollection<HttpCookie> cookies, Encoding encoding)
173		{
174			foreach (var h in items.Keys) {
175				string header = (string) h;
176				builder.Append (header);
177				builder.Append (": ");
178				builder.Append (items [header]);
179				builder.Append ("\r\n");
180			}
181
182			if (cookies != null) {
183				foreach (HttpCookie cookie in cookies) {
184					builder.Append (cookie.ToHeaderString ());
185				}
186			}
187			builder.Append ("\r\n");
188		}
189
190		public void SetHeader (string name, string value)
191		{
192			if (name == null)
193				throw new ArgumentNullException ("name");
194			
195			name = NormalizeName (name);
196
197			SetNormalizedHeader (name, value);
198		}
199
200		public void SetNormalizedHeader (string name, string value)
201		{
202			if (!IsValidHeaderName (name))
203				throw new ArgumentException (String.Format ("Invalid header '{0}'.", name));
204			
205			if (name == CONTENT_LENGTH_KEY) {
206				SetContentLength (value);
207				return;
208			}
209
210			if (value == null) {
211				items.Remove (name);
212				return;
213			}
214				
215			items [name] = value;
216		}
217
218		public static string NormalizeName (string name)
219		{
220			if (String.IsNullOrEmpty (name))
221				throw new ArgumentException ("name", "name must be a non-null non-empty string");
222			
223			StringBuilder res = null;
224
225			if (Char.IsLower (name [0])) {
226				res = new StringBuilder (name);
227				res [0] = Char.ToUpper (name [0], CultureInfo.InvariantCulture);
228			}
229			
230			char p = name [0];
231			for (int i = 1; i < name.Length; i++) {
232				char c = name [i];
233				if (p == '-' && Char.IsLower (c)) {
234					if (res == null)
235						res = new StringBuilder (name);
236					res [i] = Char.ToUpper (c, CultureInfo.InvariantCulture);
237				} else if (p != '-' && Char.IsUpper (c)) {
238					if (res == null)
239						res = new StringBuilder (name);
240					res [i] = Char.ToLower (c, CultureInfo.InvariantCulture);
241				}
242				p = c;
243			}
244
245			if (res != null)
246				return res.ToString ();
247
248			return name;
249		}
250
251		public bool IsValidHeaderName (string name)
252		{
253			// TODO: What more can I do here?
254			if (name.Length == 0)
255				return false;
256			return true;
257		}
258		
259		public void SetContentLength (string value)
260		{
261			if (value == null) {
262				items.Remove (CONTENT_LENGTH_KEY);
263				content_length = null;
264				return;
265			}
266			
267			int cl;
268			if (!Int32.TryParse (value, out cl))
269				throw new ArgumentException ("Malformed HTTP Header, invalid Content-Length value.", "value");
270			if (cl < 0)
271				throw new ArgumentException ("Content-Length must be a positive integer.", "value");
272			ContentLength = cl;
273		}
274
275		/// from mono's System.Web/HttpRequest.cs
276		public static string GetAttribute (string header_value, string attr)
277		{
278			int start = header_value.IndexOf (attr);
279			if (start == -1)
280				return null;
281
282			start += attr.Length;
283			if (start >= header_value.Length)
284				return null;
285
286			char ending = header_value [start];
287			if (ending != '"')
288				ending = ' ';
289
290			int end = header_value.IndexOf (ending, start + 1);
291			if (end == -1) {
292				// Use the full string unless its a unclosed quote
293				// TODO: What about multiline values, can they be broken across lines?
294				return (ending == '"') ? null : header_value.Substring (start);
295			}
296
297			return header_value.Substring (start + 1, end - start - 1);
298		}
299
300		/// from mono's System.Web.Util/HttpEncoder.cs
301		private static string EncodeHeaderString (string input)
302		{
303			StringBuilder sb = null;
304			char ch;
305			
306			for (int i = 0; i < input.Length; i++) {
307				ch = input [i];
308
309				if ((ch < 32 && ch != 9) || ch == 127)
310					StringBuilderAppend (String.Format ("%{0:x2}", (int)ch), ref sb);
311			}
312
313			if (sb != null)
314				return sb.ToString ();
315
316			return input;
317		}
318
319		private static void StringBuilderAppend (string s, ref StringBuilder sb)
320		{
321			if (sb == null)
322				sb = new StringBuilder (s);
323			else
324				sb.Append (s);
325		}
326	}
327}
328
329
330