1 //
2 // NameValuePair.cs: Handles the parsing of FastCGI name/value pairs.
3 //
4 // Author:
5 //   Brian Nickel (brian.nickel@gmail.com)
6 //
7 // Copyright (C) 2007 Brian Nickel
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 using System;
30 using System.Text;
31 using System.Collections.Generic;
32 using Mono.WebServer.FastCgi;
33 using Mono.WebServer.Log;
34 using Mono.WebServer.FastCgi.Compatibility;
35 
36 namespace Mono.FastCgi {
37 	public struct NameValuePair
38 	{
39 		#region Private Fields
40 
41 		readonly string name;
42 
43 		readonly string value;
44 
45 		static Encoding encoding = Encoding.Default;
46 
47 		#endregion
48 
49 
50 
51 		#region Public Fields
52 
53 		public static readonly NameValuePair Empty = new NameValuePair (
54 			null, null);
55 
56 		#endregion
57 
58 
59 
60 		#region Constructors
61 
NameValuePairMono.FastCgi.NameValuePair62 		public NameValuePair (string name, string value)
63 		{
64 			this.name  = name;
65 			this.value = value;
66 		}
67 
68 		[Obsolete]
NameValuePairMono.FastCgi.NameValuePair69 		public NameValuePair (byte [] data, ref int index)
70 		{
71 			if (data == null)
72 				throw new ArgumentNullException ("data");
73 
74 			// Name/value pairs are stored with their lengths first,
75 			// then their contents.
76 
77 			// Lengths are stored in 1 or 4 bytes depending on the
78 			// size of the contents.
79 			int name_length  = ReadLength (data.ToReadOnlyList (), ref index);
80 			int value_length = ReadLength (data.ToReadOnlyList (), ref index);
81 
82 			// Do a sanity check on the size of the data.
83 			if (index + name_length + value_length > data.Length)
84 				throw new ArgumentOutOfRangeException ("index");
85 
86 			// Make sure the encoding doesn't change while running.
87 			Encoding enc = encoding;
88 
89 			// Read the name.
90 			name = enc.GetString (data, index, name_length);
91 			index += name_length;
92 
93 			// Read the value.
94 			value = enc.GetString (data, index, value_length);
95 			index += value_length;
96 
97 			Logger.Write (LogLevel.Debug,
98 				Strings.NameValuePair_ParameterRead,
99 				name, value);
100 		}
101 
NameValuePairMono.FastCgi.NameValuePair102 		public NameValuePair(IReadOnlyList<byte> data, ref int index)
103 		{
104 			if (data == null)
105 				throw new ArgumentNullException("data");
106 
107 			// Name/value pairs are stored with their lengths first,
108 			// then their contents.
109 
110 			// Lengths are stored in 1 or 4 bytes depending on the
111 			// size of the contents.
112 			int name_length = ReadLength(data, ref index);
113 			int value_length = ReadLength(data, ref index);
114 
115 			// Do a sanity check on the size of the data.
116 			if (index + name_length + value_length > data.Count)
117 				throw new ArgumentOutOfRangeException("index");
118 
119 			// Make sure the encoding doesn't change while running.
120 			Encoding enc = encoding;
121 
122 			// Read the name.
123 			// FIXME: Please, PLEASE fix me
124 			var segment = (CompatArraySegment<byte>)data;
125 			name = enc.GetString(segment.Array, segment.Offset + index, name_length);
126 			index += name_length;
127 
128 			// Read the value.
129 			value = enc.GetString(segment.Array, segment.Offset +index, value_length);
130 			index += value_length;
131 
132 			Logger.Write(LogLevel.Debug,
133 				Strings.NameValuePair_ParameterRead,
134 				name, value);
135 		}
136 
137 		#endregion
138 
139 
140 
141 		#region Public Properties
142 
143 		public string Name {
144 			get {return name;}
145 		}
146 
147 		public string Value {
148 			get {return value;}
149 		}
150 
151 		#endregion
152 
153 
154 
155 		#region Public Static Properties
156 
157 		public static Encoding Encoding {
158 			get {return encoding;}
159 			set {encoding = value ?? Encoding.Default;}
160 		}
161 
162 		#endregion
163 
164 
165 
166 		#region Public Static Methods
167 
168 		[Obsolete]
FromDataMono.FastCgi.NameValuePair169 		public static IDictionary<string,string> FromData (byte [] data)
170 		{
171 			if (data == null)
172 				throw new ArgumentNullException ("data");
173 
174 			// Specialized.NameValueCollection would probably be
175 			// better, but it doesn't implement IDictionary.
176 			var pairs = new Dictionary<string,string> ();
177 			int index = 0;
178 
179 			// Loop through the array, reading pairs at a specified
180 			// position until the end is reached.
181 
182 			while (index < data.Length) {
183 				var pair = new NameValuePair (data, ref index);
184 
185 				if (pairs.ContainsKey (pair.Name)) {
186 					Logger.Write (LogLevel.Warning,
187 						Strings.NameValuePair_DuplicateParameter,
188 						pair.Name);
189 
190 					pairs [pair.Name] = pair.Value;
191 				} else
192 					pairs.Add (pair.Name, pair.Value);
193 			}
194 
195 			return pairs;
196 		}
197 
FromDataMono.FastCgi.NameValuePair198 		public static IDictionary<string, string> FromData(IReadOnlyList<byte> data)
199 		{
200 			if (data == null)
201 				throw new ArgumentNullException("data");
202 
203 			// Specialized.NameValueCollection would probably be
204 			// better, but it doesn't implement IDictionary.
205 			var pairs = new Dictionary<string, string>();
206 			int index = 0;
207 
208 			// Loop through the array, reading pairs at a specified
209 			// position until the end is reached.
210 
211 			while (index < data.Count)
212 			{
213 				var pair = new NameValuePair(data, ref index);
214 
215 				if (pairs.ContainsKey(pair.Name))
216 				{
217 					Logger.Write(LogLevel.Warning,
218 						Strings.NameValuePair_DuplicateParameter,
219 						pair.Name);
220 
221 					pairs[pair.Name] = pair.Value;
222 				}
223 				else
224 					pairs.Add(pair.Name, pair.Value);
225 			}
226 
227 			return pairs;
228 		}
GetDataMono.FastCgi.NameValuePair229 		public static byte [] GetData (IDictionary<string,string> pairs)
230 		{
231 			if (pairs == null)
232 				throw new ArgumentNullException ("pairs");
233 
234 			// Make sure the encoding doesn't change while running.
235 			Encoding enc = encoding;
236 
237 			// Get the total size of the new array and validate the
238 			// contents of "pairs".
239 
240 			int total_size = 0;
241 
242 			foreach (string key in pairs.Keys)
243 			{
244 				string value = pairs [key];
245 
246 				// Sanity check: "pairs" must only contain
247 				// strings.
248 				if (key == null || value == null)
249 					throw new ArgumentException (
250 						Strings.NameValuePair_DictionaryContainsNonString,
251 						"pairs");
252 
253 				int name_length = enc.GetByteCount (key);
254 				int value_length = enc.GetByteCount (value);
255 
256 				total_size += name_length > 0x7F ? 4 : 1;
257 				total_size += value_length > 0x7F ? 4 : 1;
258 				total_size += name_length + value_length;
259 			}
260 
261 			var data = new byte [total_size];
262 
263 			// Fill the data array with the data.
264 			int index = 0;
265 
266 			foreach (string key in pairs.Keys)
267 			{
268 				string value = pairs [key];
269 
270 				int name_length = enc.GetByteCount (key);
271 				int value_length = enc.GetByteCount (value);
272 
273 				WriteLength (data, ref index, name_length);
274 				WriteLength (data, ref index, value_length);
275 				index += enc.GetBytes (key, 0, key.Length, data, index);
276 				index += enc.GetBytes (value, 0, value.Length, data, index);
277 			}
278 
279 			return data;
280 		}
281 
282 		#endregion
283 
284 
285 
286 		#region Private Static Methods
287 
ReadLengthMono.FastCgi.NameValuePair288 		static int ReadLength (IReadOnlyList<byte> data, ref int index)
289 		{
290 			if (index < 0)
291 				throw new ArgumentOutOfRangeException ("index");
292 
293 			if (index >= data.Count)
294 				throw new ArgumentOutOfRangeException ("index");
295 
296 			// Lengths are stored in either 1 or 4 bytes. For
297 			// lengths under 128 bytes, which are the most common, a
298 			// single byte is used.
299 
300 			if (data [index] < 0x80)
301 				return data [index++];
302 
303 			// If the MSB for the size byte is set (value >= 0x80),
304 			// a 4 byte value is used. However, the MSB in the first
305 			// byte is not included, as it was used as an indicator.
306 
307 			if (index > data.Count - 4)
308 				throw new ArgumentOutOfRangeException ("index");
309 
310 
311 			return (0x7F & data [index++]) * 0x1000000
312 				+ data [index++] * 0x10000
313 				+ data [index++] *0x100
314 				+ data [index++];
315 
316 			// TODO: Returns zero. What gives?
317 			//return (0x7F & (int) data [index++]) << 24
318 			//	+ ((int) data [index++]) << 16
319 			//	+ ((int) data [index++]) <<  8
320 			//	+ ((int) data [index++]);
321 		}
322 
WriteLengthMono.FastCgi.NameValuePair323 		static void WriteLength (byte [] data, ref int index,
324 		                                 int length)
325 		{
326 			if (length < 0)
327 				throw new ArgumentException (
328 					Strings.NameValuePair_LengthLessThanZero,
329 					"length");
330 
331 			if (index < 0 ||
332 				index > data.Length - (length < 0x80 ? 1 : 4))
333 				throw new ArgumentOutOfRangeException ("index");
334 
335 			if (length < 0x80) {
336 				data [index++] = (byte) length;
337 				return;
338 			}
339 
340 			data [index++] = (byte)(((length & 0x7F000000) >> 24) | 0x80);
341 			data [index++] = (byte) ((length & 0xFF0000) >> 16);
342 			data [index++] = (byte) ((length & 0xFF00) >> 8);
343 			data [index++] = (byte)  (length & 0xFF);
344 		}
345 
346 		#endregion
347 	}
348 }