1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Diagnostics.Contracts; 4 using System.Text; 5 6 namespace System.Net.Http.Internal 7 { 8 /// <summary> 9 /// Helpers for encoding, decoding, and parsing URI query components. 10 /// </summary> 11 internal static class UriQueryUtility 12 { 13 // The implementation below is ported from WebUtility for use in .Net 4 14 15 #region UrlEncode implementation 16 UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue)17 private static byte[] UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) 18 { 19 byte[] encoded = UrlEncode(bytes, offset, count); 20 21 return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes)) 22 ? (byte[])encoded.Clone() 23 : encoded; 24 } 25 UrlEncode(byte[] bytes, int offset, int count)26 private static byte[] UrlEncode(byte[] bytes, int offset, int count) 27 { 28 if (!ValidateUrlEncodingParameters(bytes, offset, count)) 29 { 30 return null; 31 } 32 33 int cSpaces = 0; 34 int cUnsafe = 0; 35 36 // count them first 37 for (int i = 0; i < count; i++) 38 { 39 char ch = (char)bytes[offset + i]; 40 41 if (ch == ' ') 42 cSpaces++; 43 else if (!IsUrlSafeChar(ch)) 44 cUnsafe++; 45 } 46 47 // nothing to expand? 48 if (cSpaces == 0 && cUnsafe == 0) 49 return bytes; 50 51 // expand not 'safe' characters into %XX, spaces to +s 52 byte[] expandedBytes = new byte[count + cUnsafe * 2]; 53 int pos = 0; 54 55 for (int i = 0; i < count; i++) 56 { 57 byte b = bytes[offset + i]; 58 char ch = (char)b; 59 60 if (IsUrlSafeChar(ch)) 61 { 62 expandedBytes[pos++] = b; 63 } 64 else if (ch == ' ') 65 { 66 expandedBytes[pos++] = (byte)'+'; 67 } 68 else 69 { 70 expandedBytes[pos++] = (byte)'%'; 71 expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); 72 expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); 73 } 74 } 75 76 return expandedBytes; 77 } 78 79 #endregion 80 81 #region UrlEncode public methods 82 UrlEncode(string str)83 public static string UrlEncode(string str) 84 { 85 if (str == null) 86 return null; 87 88 byte[] bytes = Encoding.UTF8.GetBytes(str); 89 return Encoding.ASCII.GetString(UrlEncode(bytes, 0, bytes.Length, false /* alwaysCreateNewReturnValue */)); 90 } 91 UrlEncodeToBytes(byte[] bytes, int offset, int count)92 public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) 93 { 94 return UrlEncode(bytes, offset, count, true /* alwaysCreateNewReturnValue */); 95 } 96 97 #endregion 98 99 #region UrlDecode implementation 100 UrlDecodeInternal(string value, Encoding encoding)101 private static string UrlDecodeInternal(string value, Encoding encoding) 102 { 103 if (value == null) 104 { 105 return null; 106 } 107 108 int count = value.Length; 109 UrlDecoder helper = new UrlDecoder(count, encoding); 110 111 // go through the string's chars collapsing %XX and %uXXXX and 112 // appending each char as char, with exception of %XX constructs 113 // that are appended as bytes 114 115 for (int pos = 0; pos < count; pos++) 116 { 117 char ch = value[pos]; 118 119 if (ch == '+') 120 { 121 ch = ' '; 122 } 123 else if (ch == '%' && pos < count - 2) 124 { 125 if (value[pos + 1] == 'u' && pos < count - 5) 126 { 127 int h1 = HexToInt(value[pos + 2]); 128 int h2 = HexToInt(value[pos + 3]); 129 int h3 = HexToInt(value[pos + 4]); 130 int h4 = HexToInt(value[pos + 5]); 131 132 if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) 133 { // valid 4 hex chars 134 ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 135 pos += 5; 136 137 // only add as char 138 helper.AddChar(ch); 139 continue; 140 } 141 } 142 else 143 { 144 int h1 = HexToInt(value[pos + 1]); 145 int h2 = HexToInt(value[pos + 2]); 146 147 if (h1 >= 0 && h2 >= 0) 148 { // valid 2 hex chars 149 byte b = (byte)((h1 << 4) | h2); 150 pos += 2; 151 152 // don't add as char 153 helper.AddByte(b); 154 continue; 155 } 156 } 157 } 158 159 if ((ch & 0xFF80) == 0) 160 helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode 161 else 162 helper.AddChar(ch); 163 } 164 165 return helper.GetString(); 166 } 167 UrlDecodeInternal(byte[] bytes, int offset, int count)168 private static byte[] UrlDecodeInternal(byte[] bytes, int offset, int count) 169 { 170 if (!ValidateUrlEncodingParameters(bytes, offset, count)) 171 { 172 return null; 173 } 174 175 int decodedBytesCount = 0; 176 byte[] decodedBytes = new byte[count]; 177 178 for (int i = 0; i < count; i++) 179 { 180 int pos = offset + i; 181 byte b = bytes[pos]; 182 183 if (b == '+') 184 { 185 b = (byte)' '; 186 } 187 else if (b == '%' && i < count - 2) 188 { 189 int h1 = HexToInt((char)bytes[pos + 1]); 190 int h2 = HexToInt((char)bytes[pos + 2]); 191 192 if (h1 >= 0 && h2 >= 0) 193 { // valid 2 hex chars 194 b = (byte)((h1 << 4) | h2); 195 i += 2; 196 } 197 } 198 199 decodedBytes[decodedBytesCount++] = b; 200 } 201 202 if (decodedBytesCount < decodedBytes.Length) 203 { 204 byte[] newDecodedBytes = new byte[decodedBytesCount]; 205 Array.Copy(decodedBytes, newDecodedBytes, decodedBytesCount); 206 decodedBytes = newDecodedBytes; 207 } 208 209 return decodedBytes; 210 } 211 212 #endregion 213 214 #region UrlDecode public methods 215 UrlDecode(string str)216 public static string UrlDecode(string str) 217 { 218 if (str == null) 219 return null; 220 221 return UrlDecodeInternal(str, Encoding.UTF8); 222 } 223 UrlDecodeToBytes(byte[] bytes, int offset, int count)224 public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count) 225 { 226 return UrlDecodeInternal(bytes, offset, count); 227 } 228 229 #endregion 230 231 #region Helper methods 232 HexToInt(char h)233 private static int HexToInt(char h) 234 { 235 return (h >= '0' && h <= '9') ? h - '0' : 236 (h >= 'a' && h <= 'f') ? h - 'a' + 10 : 237 (h >= 'A' && h <= 'F') ? h - 'A' + 10 : 238 -1; 239 } 240 IntToHex(int n)241 private static char IntToHex(int n) 242 { 243 Contract.Assert(n < 0x10); 244 245 if (n <= 9) 246 return (char)(n + (int)'0'); 247 else 248 return (char)(n - 10 + (int)'a'); 249 } 250 251 // Set of safe chars, from RFC 1738.4 minus '+' IsUrlSafeChar(char ch)252 private static bool IsUrlSafeChar(char ch) 253 { 254 if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') 255 return true; 256 257 switch (ch) 258 { 259 case '-': 260 case '_': 261 case '.': 262 case '!': 263 case '*': 264 case '(': 265 case ')': 266 return true; 267 } 268 269 return false; 270 } 271 ValidateUrlEncodingParameters(byte[] bytes, int offset, int count)272 private static bool ValidateUrlEncodingParameters(byte[] bytes, int offset, int count) 273 { 274 if (bytes == null && count == 0) 275 return false; 276 if (bytes == null) 277 { 278 throw new ArgumentNullException("bytes"); 279 } 280 if (offset < 0 || offset > bytes.Length) 281 { 282 throw new ArgumentOutOfRangeException("offset"); 283 } 284 if (count < 0 || offset + count > bytes.Length) 285 { 286 throw new ArgumentOutOfRangeException("count"); 287 } 288 289 return true; 290 } 291 292 #endregion 293 294 #region UrlDecoder nested class 295 296 // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes 297 private class UrlDecoder 298 { 299 private int _bufferSize; 300 301 // Accumulate characters in a special array 302 private int _numChars; 303 private char[] _charBuffer; 304 305 // Accumulate bytes for decoding into characters in a special array 306 private int _numBytes; 307 private byte[] _byteBuffer; 308 309 // Encoding to convert chars to bytes 310 private Encoding _encoding; 311 FlushBytes()312 private void FlushBytes() 313 { 314 if (_numBytes > 0) 315 { 316 _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); 317 _numBytes = 0; 318 } 319 } 320 UrlDecoder(int bufferSize, Encoding encoding)321 internal UrlDecoder(int bufferSize, Encoding encoding) 322 { 323 _bufferSize = bufferSize; 324 _encoding = encoding; 325 326 _charBuffer = new char[bufferSize]; 327 // byte buffer created on demand 328 } 329 AddChar(char ch)330 internal void AddChar(char ch) 331 { 332 if (_numBytes > 0) 333 FlushBytes(); 334 335 _charBuffer[_numChars++] = ch; 336 } 337 AddByte(byte b)338 internal void AddByte(byte b) 339 { 340 if (_byteBuffer == null) 341 _byteBuffer = new byte[_bufferSize]; 342 343 _byteBuffer[_numBytes++] = b; 344 } 345 GetString()346 internal String GetString() 347 { 348 if (_numBytes > 0) 349 FlushBytes(); 350 351 if (_numChars > 0) 352 return new String(_charBuffer, 0, _numChars); 353 else 354 return String.Empty; 355 } 356 } 357 358 #endregion 359 } 360 }