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 }