1 //------------------------------------------------------------ 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //------------------------------------------------------------ 4 5 namespace System.Runtime 6 { 7 using System.Collections; 8 using System.Collections.Specialized; 9 using System.Runtime.Serialization; 10 using System.Text; 11 12 //copied from System.Web.HttpUtility code (renamed here) to remove dependency on System.Web.dll 13 static class UrlUtility 14 { 15 // Query string parsing support ParseQueryString(string query)16 public static NameValueCollection ParseQueryString(string query) 17 { 18 return ParseQueryString(query, Encoding.UTF8); 19 } 20 ParseQueryString(string query, Encoding encoding)21 public static NameValueCollection ParseQueryString(string query, Encoding encoding) 22 { 23 if (query == null) 24 { 25 throw Fx.Exception.ArgumentNull("query"); 26 } 27 28 if (encoding == null) 29 { 30 throw Fx.Exception.ArgumentNull("encoding"); 31 } 32 33 if (query.Length > 0 && query[0] == '?') 34 { 35 query = query.Substring(1); 36 } 37 38 return new HttpValueCollection(query, encoding); 39 } 40 UrlEncode(string str)41 public static string UrlEncode(string str) 42 { 43 if (str == null) 44 { 45 return null; 46 } 47 return UrlEncode(str, Encoding.UTF8); 48 } 49 50 // URL encodes a path portion of a URL string and returns the encoded string. UrlPathEncode(string str)51 public static string UrlPathEncode(string str) 52 { 53 if (str == null) 54 { 55 return null; 56 } 57 58 // recurse in case there is a query string 59 int i = str.IndexOf('?'); 60 if (i >= 0) 61 { 62 return UrlPathEncode(str.Substring(0, i)) + str.Substring(i); 63 } 64 65 // encode DBCS characters and spaces only 66 return UrlEncodeSpaces(UrlEncodeNonAscii(str, Encoding.UTF8)); 67 } 68 UrlEncode(string str, Encoding encoding)69 public static string UrlEncode(string str, Encoding encoding) 70 { 71 if (str == null) 72 { 73 return null; 74 } 75 return Encoding.ASCII.GetString(UrlEncodeToBytes(str, encoding)); 76 } 77 UrlEncodeUnicode(string str)78 public static string UrlEncodeUnicode(string str) 79 { 80 if (str == null) 81 return null; 82 return UrlEncodeUnicodeStringToStringInternal(str, false); 83 84 } 85 UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii)86 private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii) 87 { 88 int l = s.Length; 89 StringBuilder sb = new StringBuilder(l); 90 91 for (int i = 0; i < l; i++) 92 { 93 char ch = s[i]; 94 95 if ((ch & 0xff80) == 0) 96 { // 7 bit? 97 if (ignoreAscii || IsSafe(ch)) 98 { 99 sb.Append(ch); 100 } 101 else if (ch == ' ') 102 { 103 sb.Append('+'); 104 } 105 else 106 { 107 sb.Append('%'); 108 sb.Append(IntToHex((ch >> 4) & 0xf)); 109 sb.Append(IntToHex((ch) & 0xf)); 110 } 111 } 112 else 113 { // arbitrary Unicode? 114 sb.Append("%u"); 115 sb.Append(IntToHex((ch >> 12) & 0xf)); 116 sb.Append(IntToHex((ch >> 8) & 0xf)); 117 sb.Append(IntToHex((ch >> 4) & 0xf)); 118 sb.Append(IntToHex((ch) & 0xf)); 119 } 120 } 121 122 return sb.ToString(); 123 } 124 125 // Helper to encode the non-ASCII url characters only UrlEncodeNonAscii(string str, Encoding e)126 static string UrlEncodeNonAscii(string str, Encoding e) 127 { 128 if (string.IsNullOrEmpty(str)) 129 { 130 return str; 131 } 132 if (e == null) 133 { 134 e = Encoding.UTF8; 135 } 136 byte[] bytes = e.GetBytes(str); 137 bytes = UrlEncodeBytesToBytesInternalNonAscii(bytes, 0, bytes.Length, false); 138 return Encoding.ASCII.GetString(bytes); 139 } 140 141 // Helper to encode spaces only UrlEncodeSpaces(string str)142 static string UrlEncodeSpaces(string str) 143 { 144 if (str != null && str.IndexOf(' ') >= 0) 145 { 146 str = str.Replace(" ", "%20"); 147 } 148 return str; 149 } 150 UrlEncodeToBytes(string str, Encoding e)151 public static byte[] UrlEncodeToBytes(string str, Encoding e) 152 { 153 if (str == null) 154 { 155 return null; 156 } 157 byte[] bytes = e.GetBytes(str); 158 return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false); 159 } 160 161 //public static string UrlDecode(string str) 162 //{ 163 // if (str == null) 164 // return null; 165 // return UrlDecode(str, Encoding.UTF8); 166 //} 167 UrlDecode(string str, Encoding e)168 public static string UrlDecode(string str, Encoding e) 169 { 170 if (str == null) 171 { 172 return null; 173 } 174 return UrlDecodeStringFromStringInternal(str, e); 175 } 176 177 // Implementation for encoding UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)178 static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue) 179 { 180 int cSpaces = 0; 181 int cUnsafe = 0; 182 183 // count them first 184 for (int i = 0; i < count; i++) 185 { 186 char ch = (char)bytes[offset + i]; 187 188 if (ch == ' ') 189 { 190 cSpaces++; 191 } 192 else if (!IsSafe(ch)) 193 { 194 cUnsafe++; 195 } 196 } 197 198 // nothing to expand? 199 if (!alwaysCreateReturnValue && cSpaces == 0 && cUnsafe == 0) 200 { 201 return bytes; 202 } 203 204 // expand not 'safe' characters into %XX, spaces to +s 205 byte[] expandedBytes = new byte[count + cUnsafe * 2]; 206 int pos = 0; 207 208 for (int i = 0; i < count; i++) 209 { 210 byte b = bytes[offset + i]; 211 char ch = (char)b; 212 213 if (IsSafe(ch)) 214 { 215 expandedBytes[pos++] = b; 216 } 217 else if (ch == ' ') 218 { 219 expandedBytes[pos++] = (byte)'+'; 220 } 221 else 222 { 223 expandedBytes[pos++] = (byte)'%'; 224 expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); 225 expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); 226 } 227 } 228 229 return expandedBytes; 230 } 231 232 IsNonAsciiByte(byte b)233 static bool IsNonAsciiByte(byte b) 234 { 235 return (b >= 0x7F || b < 0x20); 236 } 237 UrlEncodeBytesToBytesInternalNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)238 static byte[] UrlEncodeBytesToBytesInternalNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue) 239 { 240 int cNonAscii = 0; 241 242 // count them first 243 for (int i = 0; i < count; i++) 244 { 245 if (IsNonAsciiByte(bytes[offset + i])) 246 { 247 cNonAscii++; 248 } 249 } 250 251 // nothing to expand? 252 if (!alwaysCreateReturnValue && cNonAscii == 0) 253 { 254 return bytes; 255 } 256 257 // expand not 'safe' characters into %XX, spaces to +s 258 byte[] expandedBytes = new byte[count + cNonAscii * 2]; 259 int pos = 0; 260 261 for (int i = 0; i < count; i++) 262 { 263 byte b = bytes[offset + i]; 264 265 if (IsNonAsciiByte(b)) 266 { 267 expandedBytes[pos++] = (byte)'%'; 268 expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); 269 expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); 270 } 271 else 272 { 273 expandedBytes[pos++] = b; 274 } 275 } 276 277 return expandedBytes; 278 } 279 UrlDecodeStringFromStringInternal(string s, Encoding e)280 static string UrlDecodeStringFromStringInternal(string s, Encoding e) 281 { 282 int count = s.Length; 283 UrlDecoder helper = new UrlDecoder(count, e); 284 285 // go through the string's chars collapsing %XX and %uXXXX and 286 // appending each char as char, with exception of %XX constructs 287 // that are appended as bytes 288 289 for (int pos = 0; pos < count; pos++) 290 { 291 char ch = s[pos]; 292 293 if (ch == '+') 294 { 295 ch = ' '; 296 } 297 else if (ch == '%' && pos < count - 2) 298 { 299 if (s[pos + 1] == 'u' && pos < count - 5) 300 { 301 int h1 = HexToInt(s[pos + 2]); 302 int h2 = HexToInt(s[pos + 3]); 303 int h3 = HexToInt(s[pos + 4]); 304 int h4 = HexToInt(s[pos + 5]); 305 306 if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) 307 { // valid 4 hex chars 308 ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 309 pos += 5; 310 311 // only add as char 312 helper.AddChar(ch); 313 continue; 314 } 315 } 316 else 317 { 318 int h1 = HexToInt(s[pos + 1]); 319 int h2 = HexToInt(s[pos + 2]); 320 321 if (h1 >= 0 && h2 >= 0) 322 { // valid 2 hex chars 323 byte b = (byte)((h1 << 4) | h2); 324 pos += 2; 325 326 // don't add as char 327 helper.AddByte(b); 328 continue; 329 } 330 } 331 } 332 333 if ((ch & 0xFF80) == 0) 334 { 335 helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode 336 } 337 else 338 { 339 helper.AddChar(ch); 340 } 341 } 342 343 return helper.GetString(); 344 } 345 346 // Private helpers for URL encoding/decoding HexToInt(char h)347 static int HexToInt(char h) 348 { 349 return (h >= '0' && h <= '9') ? h - '0' : 350 (h >= 'a' && h <= 'f') ? h - 'a' + 10 : 351 (h >= 'A' && h <= 'F') ? h - 'A' + 10 : 352 -1; 353 } 354 IntToHex(int n)355 static char IntToHex(int n) 356 { 357 //WCF CHANGE: CHANGED FROM Debug.Assert() to Fx.Assert() 358 Fx.Assert(n < 0x10, "n < 0x10"); 359 360 if (n <= 9) 361 { 362 return (char)(n + (int)'0'); 363 } 364 else 365 { 366 return (char)(n - 10 + (int)'a'); 367 } 368 } 369 370 // Set of safe chars, from RFC 1738.4 minus '+' IsSafe(char ch)371 internal static bool IsSafe(char ch) 372 { 373 if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') 374 { 375 return true; 376 } 377 378 switch (ch) 379 { 380 case '-': 381 case '_': 382 case '.': 383 case '!': 384 case '*': 385 case '\'': 386 case '(': 387 case ')': 388 return true; 389 } 390 391 return false; 392 } 393 394 // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes 395 class UrlDecoder 396 { 397 int _bufferSize; 398 399 // Accumulate characters in a special array 400 int _numChars; 401 char[] _charBuffer; 402 403 // Accumulate bytes for decoding into characters in a special array 404 int _numBytes; 405 byte[] _byteBuffer; 406 407 // Encoding to convert chars to bytes 408 Encoding _encoding; 409 FlushBytes()410 void FlushBytes() 411 { 412 if (_numBytes > 0) 413 { 414 _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); 415 _numBytes = 0; 416 } 417 } 418 UrlDecoder(int bufferSize, Encoding encoding)419 internal UrlDecoder(int bufferSize, Encoding encoding) 420 { 421 _bufferSize = bufferSize; 422 _encoding = encoding; 423 424 _charBuffer = new char[bufferSize]; 425 // byte buffer created on demand 426 } 427 AddChar(char ch)428 internal void AddChar(char ch) 429 { 430 if (_numBytes > 0) 431 { 432 FlushBytes(); 433 } 434 435 _charBuffer[_numChars++] = ch; 436 } 437 AddByte(byte b)438 internal void AddByte(byte b) 439 { 440 // if there are no pending bytes treat 7 bit bytes as characters 441 // this optimization is temp disable as it doesn't work for some encodings 442 443 //if (_numBytes == 0 && ((b & 0x80) == 0)) { 444 // AddChar((char)b); 445 //} 446 //else 447 448 { 449 if (_byteBuffer == null) 450 { 451 _byteBuffer = new byte[_bufferSize]; 452 } 453 454 _byteBuffer[_numBytes++] = b; 455 } 456 } 457 GetString()458 internal string GetString() 459 { 460 if (_numBytes > 0) 461 { 462 FlushBytes(); 463 } 464 465 if (_numChars > 0) 466 { 467 return new String(_charBuffer, 0, _numChars); 468 } 469 else 470 { 471 return string.Empty; 472 } 473 } 474 } 475 476 [Serializable] 477 class HttpValueCollection : NameValueCollection 478 { HttpValueCollection(string str, Encoding encoding)479 internal HttpValueCollection(string str, Encoding encoding) 480 : base(StringComparer.OrdinalIgnoreCase) 481 { 482 if (!string.IsNullOrEmpty(str)) 483 { 484 FillFromString(str, true, encoding); 485 } 486 487 IsReadOnly = false; 488 } 489 HttpValueCollection(SerializationInfo info, StreamingContext context)490 protected HttpValueCollection(SerializationInfo info, StreamingContext context) 491 : base(info, context) 492 { 493 } 494 FillFromString(string s, bool urlencoded, Encoding encoding)495 internal void FillFromString(string s, bool urlencoded, Encoding encoding) 496 { 497 int l = (s != null) ? s.Length : 0; 498 int i = 0; 499 500 while (i < l) 501 { 502 // find next & while noting first = on the way (and if there are more) 503 504 int si = i; 505 int ti = -1; 506 507 while (i < l) 508 { 509 char ch = s[i]; 510 511 if (ch == '=') 512 { 513 if (ti < 0) 514 ti = i; 515 } 516 else if (ch == '&') 517 { 518 break; 519 } 520 521 i++; 522 } 523 524 // extract the name / value pair 525 526 string name = null; 527 string value = null; 528 529 if (ti >= 0) 530 { 531 name = s.Substring(si, ti - si); 532 value = s.Substring(ti + 1, i - ti - 1); 533 } 534 else 535 { 536 value = s.Substring(si, i - si); 537 } 538 539 // add name / value pair to the collection 540 541 if (urlencoded) 542 { 543 base.Add( 544 UrlUtility.UrlDecode(name, encoding), 545 UrlUtility.UrlDecode(value, encoding)); 546 } 547 else 548 { 549 base.Add(name, value); 550 } 551 552 // trailing '&' 553 554 if (i == l - 1 && s[i] == '&') 555 { 556 base.Add(null, string.Empty); 557 } 558 559 i++; 560 } 561 } 562 ToString()563 public override string ToString() 564 { 565 return ToString(true, null); 566 } 567 ToString(bool urlencoded, IDictionary excludeKeys)568 string ToString(bool urlencoded, IDictionary excludeKeys) 569 { 570 int n = Count; 571 if (n == 0) 572 return string.Empty; 573 574 StringBuilder s = new StringBuilder(); 575 string key, keyPrefix, item; 576 577 for (int i = 0; i < n; i++) 578 { 579 key = GetKey(i); 580 581 if (excludeKeys != null && key != null && excludeKeys[key] != null) 582 { 583 continue; 584 } 585 if (urlencoded) 586 { 587 key = UrlUtility.UrlEncodeUnicode(key); 588 } 589 keyPrefix = (!string.IsNullOrEmpty(key)) ? (key + "=") : string.Empty; 590 591 ArrayList values = (ArrayList)BaseGet(i); 592 int numValues = (values != null) ? values.Count : 0; 593 594 if (s.Length > 0) 595 { 596 s.Append('&'); 597 } 598 599 if (numValues == 1) 600 { 601 s.Append(keyPrefix); 602 item = (string)values[0]; 603 if (urlencoded) 604 item = UrlUtility.UrlEncodeUnicode(item); 605 s.Append(item); 606 } 607 else if (numValues == 0) 608 { 609 s.Append(keyPrefix); 610 } 611 else 612 { 613 for (int j = 0; j < numValues; j++) 614 { 615 if (j > 0) 616 { 617 s.Append('&'); 618 } 619 s.Append(keyPrefix); 620 item = (string)values[j]; 621 if (urlencoded) 622 { 623 item = UrlUtility.UrlEncodeUnicode(item); 624 } 625 s.Append(item); 626 } 627 } 628 } 629 630 return s.ToString(); 631 } 632 } 633 } 634 } 635