1 //------------------------------------------------------------------------------ 2 // <copyright file="HttpEncoder.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 /* 8 * Base class providing extensibility hooks for custom encoding / decoding 9 * 10 * Copyright (c) 2009 Microsoft Corporation 11 */ 12 13 namespace System.Web.Util { 14 using System; 15 using System.Diagnostics.CodeAnalysis; 16 using System.Globalization; 17 using System.IO; 18 using System.Net; 19 using System.Text; 20 using System.Web; 21 using System.Web.Configuration; 22 23 public class HttpEncoder { 24 25 private static HttpEncoder _customEncoder; 26 private readonly bool _isDefaultEncoder; 27 28 private static readonly Lazy<HttpEncoder> _customEncoderResolver = 29 new Lazy<HttpEncoder>(GetCustomEncoderFromConfig); 30 31 private static readonly HttpEncoder _defaultEncoder = new HttpEncoder(); 32 33 private static readonly string[] _headerEncodingTable = new string[] { 34 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", 35 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", 36 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", 37 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f" 38 }; 39 HttpEncoder()40 public HttpEncoder() { 41 _isDefaultEncoder = (GetType() == typeof(HttpEncoder)); 42 } 43 44 public static HttpEncoder Current { 45 get { 46 // always use the fallback encoder when rendering an error page so that we can at least display *something* 47 // to the user before closing the connection 48 49 HttpContext httpContext = HttpContext.Current; 50 if (httpContext != null && httpContext.DisableCustomHttpEncoder) { 51 return _defaultEncoder; 52 } 53 else { 54 if (_customEncoder == null) { 55 _customEncoder = _customEncoderResolver.Value; 56 } 57 return _customEncoder; 58 } 59 } 60 set { 61 if (value == null) { 62 throw new ArgumentNullException("value"); 63 } 64 _customEncoder = value; 65 } 66 } 67 68 public static HttpEncoder Default { 69 get { 70 return _defaultEncoder; 71 } 72 } 73 74 internal virtual bool JavaScriptEncodeAmpersand { 75 get { 76 return !AppSettings.JavaScriptDoNotEncodeAmpersand; 77 } 78 } 79 AppendCharAsUnicodeJavaScript(StringBuilder builder, char c)80 private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c) { 81 builder.Append("\\u"); 82 builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture)); 83 } 84 CharRequiresJavaScriptEncoding(char c)85 private bool CharRequiresJavaScriptEncoding(char c) { 86 return c < 0x20 // control chars always have to be encoded 87 || c == '\"' // chars which must be encoded per JSON spec 88 || c == '\\' 89 || c == '\'' // HTML-sensitive chars encoded for safety 90 || c == '<' 91 || c == '>' 92 || (c == '&' && JavaScriptEncodeAmpersand) // Bug Dev11 #133237. Encode '&' to provide additional security for people who incorrectly call the encoding methods (unless turned off by backcompat switch) 93 || c == '\u0085' // newline chars (see Unicode 6.2, Table 5-1 [http://www.unicode.org/versions/Unicode6.2.0/ch05.pdf]) have to be encoded (DevDiv #663531) 94 || c == '\u2028' 95 || c == '\u2029'; 96 } 97 CollapsePercentUFromStringInternal(string s, Encoding e)98 internal static string CollapsePercentUFromStringInternal(string s, Encoding e) { 99 int count = s.Length; 100 UrlDecoder helper = new UrlDecoder(count, e); 101 102 // go thorugh the string's chars collapsing just %uXXXX and 103 // appending each char as char 104 int loc = s.IndexOf("%u", StringComparison.Ordinal); 105 if (loc == -1) { 106 return s; 107 } 108 109 for (int pos = 0; pos < count; pos++) { 110 char ch = s[pos]; 111 112 if (ch == '%' && pos < count - 5) { 113 if (s[pos + 1] == 'u') { 114 int h1 = HttpEncoderUtility.HexToInt(s[pos + 2]); 115 int h2 = HttpEncoderUtility.HexToInt(s[pos + 3]); 116 int h3 = HttpEncoderUtility.HexToInt(s[pos + 4]); 117 int h4 = HttpEncoderUtility.HexToInt(s[pos + 5]); 118 119 if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { //valid 4 hex chars 120 ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 121 pos += 5; 122 123 // add as char 124 helper.AddChar(ch); 125 continue; 126 } 127 } 128 } 129 if ((ch & 0xFF80) == 0) 130 helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode 131 else 132 helper.AddChar(ch); 133 } 134 return Utf16StringValidator.ValidateString(helper.GetString()); 135 } 136 GetCustomEncoderFromConfig()137 private static HttpEncoder GetCustomEncoderFromConfig() { 138 // App since this is static per AppDomain 139 RuntimeConfig config = RuntimeConfig.GetAppConfig(); 140 HttpRuntimeSection runtimeSection = config.HttpRuntime; 141 string encoderTypeName = runtimeSection.EncoderType; 142 143 // validate the type 144 Type encoderType = ConfigUtil.GetType(encoderTypeName, "encoderType", runtimeSection); 145 ConfigUtil.CheckBaseType(typeof(HttpEncoder) /* expectedBaseType */, encoderType, "encoderType", runtimeSection); 146 147 // instantiate 148 HttpEncoder encoder = (HttpEncoder)HttpRuntime.CreatePublicInstance(encoderType); 149 return encoder; 150 } 151 152 // Encode the header if it contains a CRLF pair 153 // VSWhidbey 257154 HeaderEncodeInternal(string value)154 private static string HeaderEncodeInternal(string value) { 155 string sanitizedHeader = value; 156 if (HeaderValueNeedsEncoding(value)) { 157 // DevDiv Bugs 146028 158 // Denial Of Service scenarios involving 159 // control characters are possible. 160 // We are encoding the following characters: 161 // - All CTL characters except HT (horizontal tab) 162 // - DEL character (\x7f) 163 StringBuilder sb = new StringBuilder(); 164 foreach (char c in value) { 165 if (c < 32 && c != 9) { 166 sb.Append(_headerEncodingTable[c]); 167 } 168 else if (c == 127) { 169 sb.Append("%7f"); 170 } 171 else { 172 sb.Append(c); 173 } 174 } 175 sanitizedHeader = sb.ToString(); 176 } 177 178 return sanitizedHeader; 179 } 180 181 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", 182 Justification = "Input parameter strings are immutable, so this is an appropriate way to return multiple strings.")] HeaderNameValueEncode(string headerName, string headerValue, out string encodedHeaderName, out string encodedHeaderValue)183 protected internal virtual void HeaderNameValueEncode(string headerName, string headerValue, out string encodedHeaderName, out string encodedHeaderValue) { 184 encodedHeaderName = (String.IsNullOrEmpty(headerName)) ? headerName : HeaderEncodeInternal(headerName); 185 encodedHeaderValue = (String.IsNullOrEmpty(headerValue)) ? headerValue : HeaderEncodeInternal(headerValue); 186 } 187 188 // Returns true if the string contains a control character (other than horizontal tab) or the DEL character. HeaderValueNeedsEncoding(string value)189 private static bool HeaderValueNeedsEncoding(string value) { 190 foreach (char c in value) { 191 if ((c < 32 && c != 9) || (c == 127)) { 192 return true; 193 } 194 } 195 return false; 196 } 197 HtmlAttributeEncode(string value)198 internal string HtmlAttributeEncode(string value) { 199 if (String.IsNullOrEmpty(value)) { 200 return value; 201 } 202 203 if(_isDefaultEncoder) { 204 // Don't create string writer if we don't have nothing to encode 205 int pos = IndexOfHtmlAttributeEncodingChars(value, 0); 206 if (pos == -1) { 207 return value; 208 } 209 } 210 211 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); 212 HtmlAttributeEncode(value, writer); 213 return writer.ToString(); 214 } 215 HtmlAttributeEncode(string value, TextWriter output)216 protected internal virtual void HtmlAttributeEncode(string value, TextWriter output) { 217 if (value == null) { 218 return; 219 } 220 if (output == null) { 221 throw new ArgumentNullException("output"); 222 } 223 224 // we have a special faster path for HttpWriter 225 HttpWriter httpWriter = output as HttpWriter; 226 if (httpWriter != null) { 227 HtmlAttributeEncodeInternal(value, httpWriter); 228 } 229 else { 230 HtmlAttributeEncodeInternal(value, output); 231 } 232 } 233 HtmlAttributeEncodeInternal(string value, HttpWriter writer)234 private static void HtmlAttributeEncodeInternal(string value, HttpWriter writer) { 235 int pos = IndexOfHtmlAttributeEncodingChars(value, 0); 236 if (pos == -1) { 237 writer.Write(value); 238 return; 239 } 240 241 int cch = value.Length; 242 int startPos = 0; 243 for (; ; ) { 244 if (pos > startPos) { 245 writer.WriteString(value, startPos, pos - startPos); 246 } 247 248 char ch = value[pos]; 249 switch (ch) { 250 case '"': 251 writer.Write("""); 252 break; 253 case '\'': 254 writer.Write("'"); 255 break; 256 case '&': 257 writer.Write("&"); 258 break; 259 case '<': 260 // Whidbey 32404: The character '<' is not valid in an XML attribute value. 261 // (See the W3C XML rec). 262 writer.Write("<"); 263 break; 264 } 265 266 startPos = pos + 1; 267 if (startPos >= cch) 268 break; 269 270 pos = IndexOfHtmlAttributeEncodingChars(value, startPos); 271 if (pos == -1) { 272 writer.WriteString(value, startPos, cch - startPos); 273 break; 274 } 275 } 276 } 277 HtmlAttributeEncodeInternal(String s, TextWriter output)278 private unsafe static void HtmlAttributeEncodeInternal(String s, TextWriter output) { 279 int index = IndexOfHtmlAttributeEncodingChars(s, 0); 280 if (index == -1) { 281 output.Write(s); 282 } 283 else { 284 int cch = s.Length - index; 285 fixed (char* str = s) { 286 char* pch = str; 287 while (index-- > 0) { 288 output.Write(*pch++); 289 } 290 291 while (cch-- > 0) { 292 char ch = *pch++; 293 if (ch <= '<') { 294 switch (ch) { 295 case '<': 296 output.Write("<"); 297 break; 298 case '"': 299 output.Write("""); 300 break; 301 case '\'': 302 output.Write("'"); 303 break; 304 case '&': 305 output.Write("&"); 306 break; 307 default: 308 output.Write(ch); 309 break; 310 } 311 } 312 else { 313 output.Write(ch); 314 } 315 } 316 } 317 } 318 } 319 HtmlDecode(string value)320 internal string HtmlDecode(string value) { 321 if (String.IsNullOrEmpty(value)) 322 { 323 return value; 324 } 325 326 if(_isDefaultEncoder) { 327 return WebUtility.HtmlDecode(value); 328 } 329 330 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); 331 HtmlDecode(value, writer); 332 return writer.ToString(); 333 } 334 HtmlDecode(string value, TextWriter output)335 protected internal virtual void HtmlDecode(string value, TextWriter output) { 336 WebUtility.HtmlDecode(value, output); 337 } 338 HtmlEncode(string value)339 internal string HtmlEncode(string value) { 340 if (String.IsNullOrEmpty(value)) 341 { 342 return value; 343 } 344 345 if(_isDefaultEncoder) { 346 return WebUtility.HtmlEncode(value); 347 } 348 349 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); 350 HtmlEncode(value, writer); 351 return writer.ToString(); 352 } 353 HtmlEncode(string value, TextWriter output)354 protected internal virtual void HtmlEncode(string value, TextWriter output) { 355 WebUtility.HtmlEncode(value, output); 356 } 357 IndexOfHtmlAttributeEncodingChars(string s, int startPos)358 private static unsafe int IndexOfHtmlAttributeEncodingChars(string s, int startPos) { 359 Debug.Assert(0 <= startPos && startPos <= s.Length, "0 <= startPos && startPos <= s.Length"); 360 int cch = s.Length - startPos; 361 fixed (char* str = s) { 362 for (char* pch = &str[startPos]; cch > 0; pch++, cch--) { 363 char ch = *pch; 364 if (ch <= '<') { 365 switch (ch) { 366 case '<': 367 case '"': 368 case '\'': 369 case '&': 370 return s.Length - cch; 371 } 372 } 373 } 374 } 375 376 return -1; 377 } 378 InitializeOnFirstRequest()379 internal static void InitializeOnFirstRequest() { 380 // Instantiate the encoder if it hasn't already been created. Note that this isn't storing the returned encoder 381 // anywhere; it's just priming the Lazy<T> so that future calls to the Value property getter will return quickly 382 // without going back to config. 383 384 HttpEncoder encoder = _customEncoderResolver.Value; 385 } 386 IsNonAsciiByte(byte b)387 private static bool IsNonAsciiByte(byte b) { 388 return (b >= 0x7F || b < 0x20); 389 } 390 JavaScriptStringEncode(string value)391 protected internal virtual string JavaScriptStringEncode(string value) { 392 if (String.IsNullOrEmpty(value)) { 393 return String.Empty; 394 } 395 396 StringBuilder b = null; 397 int startIndex = 0; 398 int count = 0; 399 for (int i = 0; i < value.Length; i++) { 400 char c = value[i]; 401 402 // Append the unhandled characters (that do not require special treament) 403 // to the string builder when special characters are detected. 404 if (CharRequiresJavaScriptEncoding(c)) { 405 if (b == null) { 406 b = new StringBuilder(value.Length + 5); 407 } 408 409 if (count > 0) { 410 b.Append(value, startIndex, count); 411 } 412 413 startIndex = i + 1; 414 count = 0; 415 } 416 417 switch (c) { 418 case '\r': 419 b.Append("\\r"); 420 break; 421 case '\t': 422 b.Append("\\t"); 423 break; 424 case '\"': 425 b.Append("\\\""); 426 break; 427 case '\\': 428 b.Append("\\\\"); 429 break; 430 case '\n': 431 b.Append("\\n"); 432 break; 433 case '\b': 434 b.Append("\\b"); 435 break; 436 case '\f': 437 b.Append("\\f"); 438 break; 439 default: 440 if (CharRequiresJavaScriptEncoding(c)) { 441 AppendCharAsUnicodeJavaScript(b, c); 442 } 443 else { 444 count++; 445 } 446 break; 447 } 448 } 449 450 if (b == null) { 451 return value; 452 } 453 454 if (count > 0) { 455 b.Append(value, startIndex, count); 456 } 457 458 return b.ToString(); 459 } 460 UrlDecode(byte[] bytes, int offset, int count)461 internal byte[] UrlDecode(byte[] bytes, int offset, int count) { 462 if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 463 return null; 464 } 465 466 int decodedBytesCount = 0; 467 byte[] decodedBytes = new byte[count]; 468 469 for (int i = 0; i < count; i++) { 470 int pos = offset + i; 471 byte b = bytes[pos]; 472 473 if (b == '+') { 474 b = (byte)' '; 475 } 476 else if (b == '%' && i < count - 2) { 477 int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); 478 int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); 479 480 if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars 481 b = (byte)((h1 << 4) | h2); 482 i += 2; 483 } 484 } 485 486 decodedBytes[decodedBytesCount++] = b; 487 } 488 489 if (decodedBytesCount < decodedBytes.Length) { 490 byte[] newDecodedBytes = new byte[decodedBytesCount]; 491 Array.Copy(decodedBytes, newDecodedBytes, decodedBytesCount); 492 decodedBytes = newDecodedBytes; 493 } 494 495 return decodedBytes; 496 } 497 UrlDecode(byte[] bytes, int offset, int count, Encoding encoding)498 internal string UrlDecode(byte[] bytes, int offset, int count, Encoding encoding) { 499 if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 500 return null; 501 } 502 503 UrlDecoder helper = new UrlDecoder(count, encoding); 504 505 // go through the bytes collapsing %XX and %uXXXX and appending 506 // each byte as byte, with exception of %uXXXX constructs that 507 // are appended as chars 508 509 for (int i = 0; i < count; i++) { 510 int pos = offset + i; 511 byte b = bytes[pos]; 512 513 // The code assumes that + and % cannot be in multibyte sequence 514 515 if (b == '+') { 516 b = (byte)' '; 517 } 518 else if (b == '%' && i < count - 2) { 519 if (bytes[pos + 1] == 'u' && i < count - 5) { 520 int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); 521 int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 3]); 522 int h3 = HttpEncoderUtility.HexToInt((char)bytes[pos + 4]); 523 int h4 = HttpEncoderUtility.HexToInt((char)bytes[pos + 5]); 524 525 if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { // valid 4 hex chars 526 char ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 527 i += 5; 528 529 // don't add as byte 530 helper.AddChar(ch); 531 continue; 532 } 533 } 534 else { 535 int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); 536 int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); 537 538 if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars 539 b = (byte)((h1 << 4) | h2); 540 i += 2; 541 } 542 } 543 } 544 545 helper.AddByte(b); 546 } 547 548 return Utf16StringValidator.ValidateString(helper.GetString()); 549 } 550 UrlDecode(string value, Encoding encoding)551 internal string UrlDecode(string value, Encoding encoding) { 552 if (value == null) { 553 return null; 554 } 555 556 int count = value.Length; 557 UrlDecoder helper = new UrlDecoder(count, encoding); 558 559 // go through the string's chars collapsing %XX and %uXXXX and 560 // appending each char as char, with exception of %XX constructs 561 // that are appended as bytes 562 563 for (int pos = 0; pos < count; pos++) { 564 char ch = value[pos]; 565 566 if (ch == '+') { 567 ch = ' '; 568 } 569 else if (ch == '%' && pos < count - 2) { 570 if (value[pos + 1] == 'u' && pos < count - 5) { 571 int h1 = HttpEncoderUtility.HexToInt(value[pos + 2]); 572 int h2 = HttpEncoderUtility.HexToInt(value[pos + 3]); 573 int h3 = HttpEncoderUtility.HexToInt(value[pos + 4]); 574 int h4 = HttpEncoderUtility.HexToInt(value[pos + 5]); 575 576 if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { // valid 4 hex chars 577 ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 578 pos += 5; 579 580 // only add as char 581 helper.AddChar(ch); 582 continue; 583 } 584 } 585 else { 586 int h1 = HttpEncoderUtility.HexToInt(value[pos + 1]); 587 int h2 = HttpEncoderUtility.HexToInt(value[pos + 2]); 588 589 if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars 590 byte b = (byte)((h1 << 4) | h2); 591 pos += 2; 592 593 // don't add as char 594 helper.AddByte(b); 595 continue; 596 } 597 } 598 } 599 600 if ((ch & 0xFF80) == 0) 601 helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode 602 else 603 helper.AddChar(ch); 604 } 605 606 return Utf16StringValidator.ValidateString(helper.GetString()); 607 } 608 UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue)609 internal byte[] UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) { 610 byte[] encoded = UrlEncode(bytes, offset, count); 611 612 return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes)) 613 ? (byte[])encoded.Clone() 614 : encoded; 615 } 616 UrlEncode(byte[] bytes, int offset, int count)617 protected internal virtual byte[] UrlEncode(byte[] bytes, int offset, int count) { 618 if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 619 return null; 620 } 621 622 int cSpaces = 0; 623 int cUnsafe = 0; 624 625 // count them first 626 for (int i = 0; i < count; i++) { 627 char ch = (char)bytes[offset + i]; 628 629 if (ch == ' ') 630 cSpaces++; 631 else if (!HttpEncoderUtility.IsUrlSafeChar(ch)) 632 cUnsafe++; 633 } 634 635 // nothing to expand? 636 if (cSpaces == 0 && cUnsafe == 0) { 637 // DevDiv 912606: respect "offset" and "count" 638 if (0 == offset && bytes.Length == count) { 639 return bytes; 640 } 641 else { 642 var subarray = new byte[count]; 643 Buffer.BlockCopy(bytes, offset, subarray, 0, count); 644 return subarray; 645 } 646 } 647 648 // expand not 'safe' characters into %XX, spaces to +s 649 byte[] expandedBytes = new byte[count + cUnsafe * 2]; 650 int pos = 0; 651 652 for (int i = 0; i < count; i++) { 653 byte b = bytes[offset + i]; 654 char ch = (char)b; 655 656 if (HttpEncoderUtility.IsUrlSafeChar(ch)) { 657 expandedBytes[pos++] = b; 658 } 659 else if (ch == ' ') { 660 expandedBytes[pos++] = (byte)'+'; 661 } 662 else { 663 expandedBytes[pos++] = (byte)'%'; 664 expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); 665 expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); 666 } 667 } 668 669 return expandedBytes; 670 } 671 672 // Helper to encode the non-ASCII url characters only UrlEncodeNonAscii(string str, Encoding e)673 internal String UrlEncodeNonAscii(string str, Encoding e) { 674 if (String.IsNullOrEmpty(str)) 675 return str; 676 if (e == null) 677 e = Encoding.UTF8; 678 byte[] bytes = e.GetBytes(str); 679 byte[] encodedBytes = UrlEncodeNonAscii(bytes, 0, bytes.Length, false /* alwaysCreateNewReturnValue */); 680 return Encoding.ASCII.GetString(encodedBytes); 681 } 682 UrlEncodeNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue)683 internal byte[] UrlEncodeNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) { 684 if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 685 return null; 686 } 687 688 int cNonAscii = 0; 689 690 // count them first 691 for (int i = 0; i < count; i++) { 692 if (IsNonAsciiByte(bytes[offset + i])) 693 cNonAscii++; 694 } 695 696 // nothing to expand? 697 if (!alwaysCreateNewReturnValue && cNonAscii == 0) 698 return bytes; 699 700 // expand not 'safe' characters into %XX, spaces to +s 701 byte[] expandedBytes = new byte[count + cNonAscii * 2]; 702 int pos = 0; 703 704 for (int i = 0; i < count; i++) { 705 byte b = bytes[offset + i]; 706 707 if (IsNonAsciiByte(b)) { 708 expandedBytes[pos++] = (byte)'%'; 709 expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); 710 expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); 711 } 712 else { 713 expandedBytes[pos++] = b; 714 } 715 } 716 717 return expandedBytes; 718 } 719 720 [Obsolete("This method produces non-standards-compliant output and has interoperability issues. The preferred alternative is UrlEncode(*).")] UrlEncodeUnicode(string value, bool ignoreAscii)721 internal string UrlEncodeUnicode(string value, bool ignoreAscii) { 722 if (value == null) { 723 return null; 724 } 725 726 int l = value.Length; 727 StringBuilder sb = new StringBuilder(l); 728 729 for (int i = 0; i < l; i++) { 730 char ch = value[i]; 731 732 if ((ch & 0xff80) == 0) { // 7 bit? 733 if (ignoreAscii || HttpEncoderUtility.IsUrlSafeChar(ch)) { 734 sb.Append(ch); 735 } 736 else if (ch == ' ') { 737 sb.Append('+'); 738 } 739 else { 740 sb.Append('%'); 741 sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); 742 sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); 743 } 744 } 745 else { // arbitrary Unicode? 746 sb.Append("%u"); 747 sb.Append(HttpEncoderUtility.IntToHex((ch >> 12) & 0xf)); 748 sb.Append(HttpEncoderUtility.IntToHex((ch >> 8) & 0xf)); 749 sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); 750 sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); 751 } 752 } 753 754 return sb.ToString(); 755 } 756 757 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", 758 Justification = "Does not represent an entire URL, just a portion.")] UrlPathEncode(string value)759 protected internal virtual string UrlPathEncode(string value) { 760 // DevDiv 995259: HttpUtility.UrlPathEncode should not encode IDN part of the url 761 if (BinaryCompatibility.Current.TargetsAtLeastFramework46) { 762 if (String.IsNullOrEmpty(value)) { 763 return value; 764 } 765 766 string schemeAndAuthority; 767 string path; 768 string queryAndFragment; 769 bool isValidUrl = UriUtil.TrySplitUriForPathEncode(value, out schemeAndAuthority, out path, out queryAndFragment, checkScheme: false); 770 771 if (!isValidUrl) { 772 // If the value is not a valid url, we treat it as a relative url. 773 // We don't need to extract query string from the url since UrlPathEncode() 774 // does not encode query string. 775 schemeAndAuthority = null; 776 path = value; 777 queryAndFragment = null; 778 } 779 780 return schemeAndAuthority + UrlPathEncodeImpl(path) + queryAndFragment; 781 } 782 else { 783 return UrlPathEncodeImpl(value); 784 } 785 } 786 787 // This is the original UrlPathEncode(string) 788 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", 789 Justification = "Does not represent an entire URL, just a portion.")] UrlPathEncodeImpl(string value)790 private string UrlPathEncodeImpl(string value) { 791 if (String.IsNullOrEmpty(value)) { 792 return value; 793 } 794 795 // recurse in case there is a query string 796 int i = value.IndexOf('?'); 797 if (i >= 0) 798 return UrlPathEncodeImpl(value.Substring(0, i)) + value.Substring(i); 799 800 // encode DBCS characters and spaces only 801 return HttpEncoderUtility.UrlEncodeSpaces(UrlEncodeNonAscii(value, Encoding.UTF8)); 802 } 803 UrlTokenDecode(string input)804 internal byte[] UrlTokenDecode(string input) { 805 if (input == null) 806 throw new ArgumentNullException("input"); 807 808 int len = input.Length; 809 if (len < 1) 810 return new byte[0]; 811 812 /////////////////////////////////////////////////////////////////// 813 // Step 1: Calculate the number of padding chars to append to this string. 814 // The number of padding chars to append is stored in the last char of the string. 815 int numPadChars = (int)input[len - 1] - (int)'0'; 816 if (numPadChars < 0 || numPadChars > 10) 817 return null; 818 819 820 /////////////////////////////////////////////////////////////////// 821 // Step 2: Create array to store the chars (not including the last char) 822 // and the padding chars 823 char[] base64Chars = new char[len - 1 + numPadChars]; 824 825 826 //////////////////////////////////////////////////////// 827 // Step 3: Copy in the chars. Transform the "-" to "+", and "*" to "/" 828 for (int iter = 0; iter < len - 1; iter++) { 829 char c = input[iter]; 830 831 switch (c) { 832 case '-': 833 base64Chars[iter] = '+'; 834 break; 835 836 case '_': 837 base64Chars[iter] = '/'; 838 break; 839 840 default: 841 base64Chars[iter] = c; 842 break; 843 } 844 } 845 846 //////////////////////////////////////////////////////// 847 // Step 4: Add padding chars 848 for (int iter = len - 1; iter < base64Chars.Length; iter++) { 849 base64Chars[iter] = '='; 850 } 851 852 // Do the actual conversion 853 return Convert.FromBase64CharArray(base64Chars, 0, base64Chars.Length); 854 } 855 UrlTokenEncode(byte[] input)856 internal string UrlTokenEncode(byte[] input) { 857 if (input == null) 858 throw new ArgumentNullException("input"); 859 if (input.Length < 1) 860 return String.Empty; 861 862 string base64Str = null; 863 int endPos = 0; 864 char[] base64Chars = null; 865 866 //////////////////////////////////////////////////////// 867 // Step 1: Do a Base64 encoding 868 base64Str = Convert.ToBase64String(input); 869 if (base64Str == null) 870 return null; 871 872 //////////////////////////////////////////////////////// 873 // Step 2: Find how many padding chars are present in the end 874 for (endPos = base64Str.Length; endPos > 0; endPos--) { 875 if (base64Str[endPos - 1] != '=') // Found a non-padding char! 876 { 877 break; // Stop here 878 } 879 } 880 881 //////////////////////////////////////////////////////// 882 // Step 3: Create char array to store all non-padding chars, 883 // plus a char to indicate how many padding chars are needed 884 base64Chars = new char[endPos + 1]; 885 base64Chars[endPos] = (char)((int)'0' + base64Str.Length - endPos); // Store a char at the end, to indicate how many padding chars are needed 886 887 //////////////////////////////////////////////////////// 888 // Step 3: Copy in the other chars. Transform the "+" to "-", and "/" to "_" 889 for (int iter = 0; iter < endPos; iter++) { 890 char c = base64Str[iter]; 891 892 switch (c) { 893 case '+': 894 base64Chars[iter] = '-'; 895 break; 896 897 case '/': 898 base64Chars[iter] = '_'; 899 break; 900 901 case '=': 902 Debug.Assert(false); 903 base64Chars[iter] = c; 904 break; 905 906 default: 907 base64Chars[iter] = c; 908 break; 909 } 910 } 911 return new string(base64Chars); 912 } 913 ValidateUrlEncodingParameters(byte[] bytes, int offset, int count)914 internal static bool ValidateUrlEncodingParameters(byte[] bytes, int offset, int count) { 915 if (bytes == null && count == 0) 916 return false; 917 if (bytes == null) { 918 throw new ArgumentNullException("bytes"); 919 } 920 if (offset < 0 || offset > bytes.Length) { 921 throw new ArgumentOutOfRangeException("offset"); 922 } 923 if (count < 0 || offset + count > bytes.Length) { 924 throw new ArgumentOutOfRangeException("count"); 925 } 926 927 return true; 928 } 929 930 // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes 931 private class UrlDecoder { 932 private int _bufferSize; 933 934 // Accumulate characters in a special array 935 private int _numChars; 936 private char[] _charBuffer; 937 938 // Accumulate bytes for decoding into characters in a special array 939 private int _numBytes; 940 private byte[] _byteBuffer; 941 942 // Encoding to convert chars to bytes 943 private Encoding _encoding; 944 FlushBytes()945 private void FlushBytes() { 946 if (_numBytes > 0) { 947 _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); 948 _numBytes = 0; 949 } 950 } 951 UrlDecoder(int bufferSize, Encoding encoding)952 internal UrlDecoder(int bufferSize, Encoding encoding) { 953 _bufferSize = bufferSize; 954 _encoding = encoding; 955 956 _charBuffer = new char[bufferSize]; 957 // byte buffer created on demand 958 } 959 AddChar(char ch)960 internal void AddChar(char ch) { 961 if (_numBytes > 0) 962 FlushBytes(); 963 964 _charBuffer[_numChars++] = ch; 965 } 966 AddByte(byte b)967 internal void AddByte(byte b) { 968 // if there are no pending bytes treat 7 bit bytes as characters 969 // this optimization is temp disable as it doesn't work for some encodings 970 /* 971 if (_numBytes == 0 && ((b & 0x80) == 0)) { 972 AddChar((char)b); 973 } 974 else 975 */ 976 { 977 if (_byteBuffer == null) 978 _byteBuffer = new byte[_bufferSize]; 979 980 _byteBuffer[_numBytes++] = b; 981 } 982 } 983 GetString()984 internal String GetString() { 985 if (_numBytes > 0) 986 FlushBytes(); 987 988 if (_numChars > 0) 989 return new String(_charBuffer, 0, _numChars); 990 else 991 return String.Empty; 992 } 993 } 994 995 } 996 } 997