//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * Base class providing extensibility hooks for custom encoding / decoding * * Copyright (c) 2009 Microsoft Corporation */ namespace System.Web.Util { using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Net; using System.Text; using System.Web; using System.Web.Configuration; public class HttpEncoder { private static HttpEncoder _customEncoder; private readonly bool _isDefaultEncoder; private static readonly Lazy _customEncoderResolver = new Lazy(GetCustomEncoderFromConfig); private static readonly HttpEncoder _defaultEncoder = new HttpEncoder(); private static readonly string[] _headerEncodingTable = new string[] { "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f" }; public HttpEncoder() { _isDefaultEncoder = (GetType() == typeof(HttpEncoder)); } public static HttpEncoder Current { get { // always use the fallback encoder when rendering an error page so that we can at least display *something* // to the user before closing the connection HttpContext httpContext = HttpContext.Current; if (httpContext != null && httpContext.DisableCustomHttpEncoder) { return _defaultEncoder; } else { if (_customEncoder == null) { _customEncoder = _customEncoderResolver.Value; } return _customEncoder; } } set { if (value == null) { throw new ArgumentNullException("value"); } _customEncoder = value; } } public static HttpEncoder Default { get { return _defaultEncoder; } } internal virtual bool JavaScriptEncodeAmpersand { get { return !AppSettings.JavaScriptDoNotEncodeAmpersand; } } private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c) { builder.Append("\\u"); builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture)); } private bool CharRequiresJavaScriptEncoding(char c) { return c < 0x20 // control chars always have to be encoded || c == '\"' // chars which must be encoded per JSON spec || c == '\\' || c == '\'' // HTML-sensitive chars encoded for safety || c == '<' || c == '>' || (c == '&' && JavaScriptEncodeAmpersand) // Bug Dev11 #133237. Encode '&' to provide additional security for people who incorrectly call the encoding methods (unless turned off by backcompat switch) || 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) || c == '\u2028' || c == '\u2029'; } internal static string CollapsePercentUFromStringInternal(string s, Encoding e) { int count = s.Length; UrlDecoder helper = new UrlDecoder(count, e); // go thorugh the string's chars collapsing just %uXXXX and // appending each char as char int loc = s.IndexOf("%u", StringComparison.Ordinal); if (loc == -1) { return s; } for (int pos = 0; pos < count; pos++) { char ch = s[pos]; if (ch == '%' && pos < count - 5) { if (s[pos + 1] == 'u') { int h1 = HttpEncoderUtility.HexToInt(s[pos + 2]); int h2 = HttpEncoderUtility.HexToInt(s[pos + 3]); int h3 = HttpEncoderUtility.HexToInt(s[pos + 4]); int h4 = HttpEncoderUtility.HexToInt(s[pos + 5]); if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { //valid 4 hex chars ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); pos += 5; // add as char helper.AddChar(ch); continue; } } } if ((ch & 0xFF80) == 0) helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode else helper.AddChar(ch); } return Utf16StringValidator.ValidateString(helper.GetString()); } private static HttpEncoder GetCustomEncoderFromConfig() { // App since this is static per AppDomain RuntimeConfig config = RuntimeConfig.GetAppConfig(); HttpRuntimeSection runtimeSection = config.HttpRuntime; string encoderTypeName = runtimeSection.EncoderType; // validate the type Type encoderType = ConfigUtil.GetType(encoderTypeName, "encoderType", runtimeSection); ConfigUtil.CheckBaseType(typeof(HttpEncoder) /* expectedBaseType */, encoderType, "encoderType", runtimeSection); // instantiate HttpEncoder encoder = (HttpEncoder)HttpRuntime.CreatePublicInstance(encoderType); return encoder; } // Encode the header if it contains a CRLF pair // VSWhidbey 257154 private static string HeaderEncodeInternal(string value) { string sanitizedHeader = value; if (HeaderValueNeedsEncoding(value)) { // DevDiv Bugs 146028 // Denial Of Service scenarios involving // control characters are possible. // We are encoding the following characters: // - All CTL characters except HT (horizontal tab) // - DEL character (\x7f) StringBuilder sb = new StringBuilder(); foreach (char c in value) { if (c < 32 && c != 9) { sb.Append(_headerEncodingTable[c]); } else if (c == 127) { sb.Append("%7f"); } else { sb.Append(c); } } sanitizedHeader = sb.ToString(); } return sanitizedHeader; } [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Input parameter strings are immutable, so this is an appropriate way to return multiple strings.")] protected internal virtual void HeaderNameValueEncode(string headerName, string headerValue, out string encodedHeaderName, out string encodedHeaderValue) { encodedHeaderName = (String.IsNullOrEmpty(headerName)) ? headerName : HeaderEncodeInternal(headerName); encodedHeaderValue = (String.IsNullOrEmpty(headerValue)) ? headerValue : HeaderEncodeInternal(headerValue); } // Returns true if the string contains a control character (other than horizontal tab) or the DEL character. private static bool HeaderValueNeedsEncoding(string value) { foreach (char c in value) { if ((c < 32 && c != 9) || (c == 127)) { return true; } } return false; } internal string HtmlAttributeEncode(string value) { if (String.IsNullOrEmpty(value)) { return value; } if(_isDefaultEncoder) { // Don't create string writer if we don't have nothing to encode int pos = IndexOfHtmlAttributeEncodingChars(value, 0); if (pos == -1) { return value; } } StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); HtmlAttributeEncode(value, writer); return writer.ToString(); } protected internal virtual void HtmlAttributeEncode(string value, TextWriter output) { if (value == null) { return; } if (output == null) { throw new ArgumentNullException("output"); } // we have a special faster path for HttpWriter HttpWriter httpWriter = output as HttpWriter; if (httpWriter != null) { HtmlAttributeEncodeInternal(value, httpWriter); } else { HtmlAttributeEncodeInternal(value, output); } } private static void HtmlAttributeEncodeInternal(string value, HttpWriter writer) { int pos = IndexOfHtmlAttributeEncodingChars(value, 0); if (pos == -1) { writer.Write(value); return; } int cch = value.Length; int startPos = 0; for (; ; ) { if (pos > startPos) { writer.WriteString(value, startPos, pos - startPos); } char ch = value[pos]; switch (ch) { case '"': writer.Write("""); break; case '\'': writer.Write("'"); break; case '&': writer.Write("&"); break; case '<': // Whidbey 32404: The character '<' is not valid in an XML attribute value. // (See the W3C XML rec). writer.Write("<"); break; } startPos = pos + 1; if (startPos >= cch) break; pos = IndexOfHtmlAttributeEncodingChars(value, startPos); if (pos == -1) { writer.WriteString(value, startPos, cch - startPos); break; } } } private unsafe static void HtmlAttributeEncodeInternal(String s, TextWriter output) { int index = IndexOfHtmlAttributeEncodingChars(s, 0); if (index == -1) { output.Write(s); } else { int cch = s.Length - index; fixed (char* str = s) { char* pch = str; while (index-- > 0) { output.Write(*pch++); } while (cch-- > 0) { char ch = *pch++; if (ch <= '<') { switch (ch) { case '<': output.Write("<"); break; case '"': output.Write("""); break; case '\'': output.Write("'"); break; case '&': output.Write("&"); break; default: output.Write(ch); break; } } else { output.Write(ch); } } } } } internal string HtmlDecode(string value) { if (String.IsNullOrEmpty(value)) { return value; } if(_isDefaultEncoder) { return WebUtility.HtmlDecode(value); } StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); HtmlDecode(value, writer); return writer.ToString(); } protected internal virtual void HtmlDecode(string value, TextWriter output) { WebUtility.HtmlDecode(value, output); } internal string HtmlEncode(string value) { if (String.IsNullOrEmpty(value)) { return value; } if(_isDefaultEncoder) { return WebUtility.HtmlEncode(value); } StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); HtmlEncode(value, writer); return writer.ToString(); } protected internal virtual void HtmlEncode(string value, TextWriter output) { WebUtility.HtmlEncode(value, output); } private static unsafe int IndexOfHtmlAttributeEncodingChars(string s, int startPos) { Debug.Assert(0 <= startPos && startPos <= s.Length, "0 <= startPos && startPos <= s.Length"); int cch = s.Length - startPos; fixed (char* str = s) { for (char* pch = &str[startPos]; cch > 0; pch++, cch--) { char ch = *pch; if (ch <= '<') { switch (ch) { case '<': case '"': case '\'': case '&': return s.Length - cch; } } } } return -1; } internal static void InitializeOnFirstRequest() { // Instantiate the encoder if it hasn't already been created. Note that this isn't storing the returned encoder // anywhere; it's just priming the Lazy so that future calls to the Value property getter will return quickly // without going back to config. HttpEncoder encoder = _customEncoderResolver.Value; } private static bool IsNonAsciiByte(byte b) { return (b >= 0x7F || b < 0x20); } protected internal virtual string JavaScriptStringEncode(string value) { if (String.IsNullOrEmpty(value)) { return String.Empty; } StringBuilder b = null; int startIndex = 0; int count = 0; for (int i = 0; i < value.Length; i++) { char c = value[i]; // Append the unhandled characters (that do not require special treament) // to the string builder when special characters are detected. if (CharRequiresJavaScriptEncoding(c)) { if (b == null) { b = new StringBuilder(value.Length + 5); } if (count > 0) { b.Append(value, startIndex, count); } startIndex = i + 1; count = 0; } switch (c) { case '\r': b.Append("\\r"); break; case '\t': b.Append("\\t"); break; case '\"': b.Append("\\\""); break; case '\\': b.Append("\\\\"); break; case '\n': b.Append("\\n"); break; case '\b': b.Append("\\b"); break; case '\f': b.Append("\\f"); break; default: if (CharRequiresJavaScriptEncoding(c)) { AppendCharAsUnicodeJavaScript(b, c); } else { count++; } break; } } if (b == null) { return value; } if (count > 0) { b.Append(value, startIndex, count); } return b.ToString(); } internal byte[] UrlDecode(byte[] bytes, int offset, int count) { if (!ValidateUrlEncodingParameters(bytes, offset, count)) { return null; } int decodedBytesCount = 0; byte[] decodedBytes = new byte[count]; for (int i = 0; i < count; i++) { int pos = offset + i; byte b = bytes[pos]; if (b == '+') { b = (byte)' '; } else if (b == '%' && i < count - 2) { int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars b = (byte)((h1 << 4) | h2); i += 2; } } decodedBytes[decodedBytesCount++] = b; } if (decodedBytesCount < decodedBytes.Length) { byte[] newDecodedBytes = new byte[decodedBytesCount]; Array.Copy(decodedBytes, newDecodedBytes, decodedBytesCount); decodedBytes = newDecodedBytes; } return decodedBytes; } internal string UrlDecode(byte[] bytes, int offset, int count, Encoding encoding) { if (!ValidateUrlEncodingParameters(bytes, offset, count)) { return null; } UrlDecoder helper = new UrlDecoder(count, encoding); // go through the bytes collapsing %XX and %uXXXX and appending // each byte as byte, with exception of %uXXXX constructs that // are appended as chars for (int i = 0; i < count; i++) { int pos = offset + i; byte b = bytes[pos]; // The code assumes that + and % cannot be in multibyte sequence if (b == '+') { b = (byte)' '; } else if (b == '%' && i < count - 2) { if (bytes[pos + 1] == 'u' && i < count - 5) { int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 3]); int h3 = HttpEncoderUtility.HexToInt((char)bytes[pos + 4]); int h4 = HttpEncoderUtility.HexToInt((char)bytes[pos + 5]); if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { // valid 4 hex chars char ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); i += 5; // don't add as byte helper.AddChar(ch); continue; } } else { int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars b = (byte)((h1 << 4) | h2); i += 2; } } } helper.AddByte(b); } return Utf16StringValidator.ValidateString(helper.GetString()); } internal string UrlDecode(string value, Encoding encoding) { if (value == null) { return null; } int count = value.Length; UrlDecoder helper = new UrlDecoder(count, encoding); // go through the string's chars collapsing %XX and %uXXXX and // appending each char as char, with exception of %XX constructs // that are appended as bytes for (int pos = 0; pos < count; pos++) { char ch = value[pos]; if (ch == '+') { ch = ' '; } else if (ch == '%' && pos < count - 2) { if (value[pos + 1] == 'u' && pos < count - 5) { int h1 = HttpEncoderUtility.HexToInt(value[pos + 2]); int h2 = HttpEncoderUtility.HexToInt(value[pos + 3]); int h3 = HttpEncoderUtility.HexToInt(value[pos + 4]); int h4 = HttpEncoderUtility.HexToInt(value[pos + 5]); if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { // valid 4 hex chars ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); pos += 5; // only add as char helper.AddChar(ch); continue; } } else { int h1 = HttpEncoderUtility.HexToInt(value[pos + 1]); int h2 = HttpEncoderUtility.HexToInt(value[pos + 2]); if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars byte b = (byte)((h1 << 4) | h2); pos += 2; // don't add as char helper.AddByte(b); continue; } } } if ((ch & 0xFF80) == 0) helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode else helper.AddChar(ch); } return Utf16StringValidator.ValidateString(helper.GetString()); } internal byte[] UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) { byte[] encoded = UrlEncode(bytes, offset, count); return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes)) ? (byte[])encoded.Clone() : encoded; } protected internal virtual byte[] UrlEncode(byte[] bytes, int offset, int count) { if (!ValidateUrlEncodingParameters(bytes, offset, count)) { return null; } int cSpaces = 0; int cUnsafe = 0; // count them first for (int i = 0; i < count; i++) { char ch = (char)bytes[offset + i]; if (ch == ' ') cSpaces++; else if (!HttpEncoderUtility.IsUrlSafeChar(ch)) cUnsafe++; } // nothing to expand? if (cSpaces == 0 && cUnsafe == 0) { // DevDiv 912606: respect "offset" and "count" if (0 == offset && bytes.Length == count) { return bytes; } else { var subarray = new byte[count]; Buffer.BlockCopy(bytes, offset, subarray, 0, count); return subarray; } } // expand not 'safe' characters into %XX, spaces to +s byte[] expandedBytes = new byte[count + cUnsafe * 2]; int pos = 0; for (int i = 0; i < count; i++) { byte b = bytes[offset + i]; char ch = (char)b; if (HttpEncoderUtility.IsUrlSafeChar(ch)) { expandedBytes[pos++] = b; } else if (ch == ' ') { expandedBytes[pos++] = (byte)'+'; } else { expandedBytes[pos++] = (byte)'%'; expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); } } return expandedBytes; } // Helper to encode the non-ASCII url characters only internal String UrlEncodeNonAscii(string str, Encoding e) { if (String.IsNullOrEmpty(str)) return str; if (e == null) e = Encoding.UTF8; byte[] bytes = e.GetBytes(str); byte[] encodedBytes = UrlEncodeNonAscii(bytes, 0, bytes.Length, false /* alwaysCreateNewReturnValue */); return Encoding.ASCII.GetString(encodedBytes); } internal byte[] UrlEncodeNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) { if (!ValidateUrlEncodingParameters(bytes, offset, count)) { return null; } int cNonAscii = 0; // count them first for (int i = 0; i < count; i++) { if (IsNonAsciiByte(bytes[offset + i])) cNonAscii++; } // nothing to expand? if (!alwaysCreateNewReturnValue && cNonAscii == 0) return bytes; // expand not 'safe' characters into %XX, spaces to +s byte[] expandedBytes = new byte[count + cNonAscii * 2]; int pos = 0; for (int i = 0; i < count; i++) { byte b = bytes[offset + i]; if (IsNonAsciiByte(b)) { expandedBytes[pos++] = (byte)'%'; expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); } else { expandedBytes[pos++] = b; } } return expandedBytes; } [Obsolete("This method produces non-standards-compliant output and has interoperability issues. The preferred alternative is UrlEncode(*).")] internal string UrlEncodeUnicode(string value, bool ignoreAscii) { if (value == null) { return null; } int l = value.Length; StringBuilder sb = new StringBuilder(l); for (int i = 0; i < l; i++) { char ch = value[i]; if ((ch & 0xff80) == 0) { // 7 bit? if (ignoreAscii || HttpEncoderUtility.IsUrlSafeChar(ch)) { sb.Append(ch); } else if (ch == ' ') { sb.Append('+'); } else { sb.Append('%'); sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); } } else { // arbitrary Unicode? sb.Append("%u"); sb.Append(HttpEncoderUtility.IntToHex((ch >> 12) & 0xf)); sb.Append(HttpEncoderUtility.IntToHex((ch >> 8) & 0xf)); sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); } } return sb.ToString(); } [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Does not represent an entire URL, just a portion.")] protected internal virtual string UrlPathEncode(string value) { // DevDiv 995259: HttpUtility.UrlPathEncode should not encode IDN part of the url if (BinaryCompatibility.Current.TargetsAtLeastFramework46) { if (String.IsNullOrEmpty(value)) { return value; } string schemeAndAuthority; string path; string queryAndFragment; bool isValidUrl = UriUtil.TrySplitUriForPathEncode(value, out schemeAndAuthority, out path, out queryAndFragment, checkScheme: false); if (!isValidUrl) { // If the value is not a valid url, we treat it as a relative url. // We don't need to extract query string from the url since UrlPathEncode() // does not encode query string. schemeAndAuthority = null; path = value; queryAndFragment = null; } return schemeAndAuthority + UrlPathEncodeImpl(path) + queryAndFragment; } else { return UrlPathEncodeImpl(value); } } // This is the original UrlPathEncode(string) [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Does not represent an entire URL, just a portion.")] private string UrlPathEncodeImpl(string value) { if (String.IsNullOrEmpty(value)) { return value; } // recurse in case there is a query string int i = value.IndexOf('?'); if (i >= 0) return UrlPathEncodeImpl(value.Substring(0, i)) + value.Substring(i); // encode DBCS characters and spaces only return HttpEncoderUtility.UrlEncodeSpaces(UrlEncodeNonAscii(value, Encoding.UTF8)); } internal byte[] UrlTokenDecode(string input) { if (input == null) throw new ArgumentNullException("input"); int len = input.Length; if (len < 1) return new byte[0]; /////////////////////////////////////////////////////////////////// // Step 1: Calculate the number of padding chars to append to this string. // The number of padding chars to append is stored in the last char of the string. int numPadChars = (int)input[len - 1] - (int)'0'; if (numPadChars < 0 || numPadChars > 10) return null; /////////////////////////////////////////////////////////////////// // Step 2: Create array to store the chars (not including the last char) // and the padding chars char[] base64Chars = new char[len - 1 + numPadChars]; //////////////////////////////////////////////////////// // Step 3: Copy in the chars. Transform the "-" to "+", and "*" to "/" for (int iter = 0; iter < len - 1; iter++) { char c = input[iter]; switch (c) { case '-': base64Chars[iter] = '+'; break; case '_': base64Chars[iter] = '/'; break; default: base64Chars[iter] = c; break; } } //////////////////////////////////////////////////////// // Step 4: Add padding chars for (int iter = len - 1; iter < base64Chars.Length; iter++) { base64Chars[iter] = '='; } // Do the actual conversion return Convert.FromBase64CharArray(base64Chars, 0, base64Chars.Length); } internal string UrlTokenEncode(byte[] input) { if (input == null) throw new ArgumentNullException("input"); if (input.Length < 1) return String.Empty; string base64Str = null; int endPos = 0; char[] base64Chars = null; //////////////////////////////////////////////////////// // Step 1: Do a Base64 encoding base64Str = Convert.ToBase64String(input); if (base64Str == null) return null; //////////////////////////////////////////////////////// // Step 2: Find how many padding chars are present in the end for (endPos = base64Str.Length; endPos > 0; endPos--) { if (base64Str[endPos - 1] != '=') // Found a non-padding char! { break; // Stop here } } //////////////////////////////////////////////////////// // Step 3: Create char array to store all non-padding chars, // plus a char to indicate how many padding chars are needed base64Chars = new char[endPos + 1]; base64Chars[endPos] = (char)((int)'0' + base64Str.Length - endPos); // Store a char at the end, to indicate how many padding chars are needed //////////////////////////////////////////////////////// // Step 3: Copy in the other chars. Transform the "+" to "-", and "/" to "_" for (int iter = 0; iter < endPos; iter++) { char c = base64Str[iter]; switch (c) { case '+': base64Chars[iter] = '-'; break; case '/': base64Chars[iter] = '_'; break; case '=': Debug.Assert(false); base64Chars[iter] = c; break; default: base64Chars[iter] = c; break; } } return new string(base64Chars); } internal static bool ValidateUrlEncodingParameters(byte[] bytes, int offset, int count) { if (bytes == null && count == 0) return false; if (bytes == null) { throw new ArgumentNullException("bytes"); } if (offset < 0 || offset > bytes.Length) { throw new ArgumentOutOfRangeException("offset"); } if (count < 0 || offset + count > bytes.Length) { throw new ArgumentOutOfRangeException("count"); } return true; } // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes private class UrlDecoder { private int _bufferSize; // Accumulate characters in a special array private int _numChars; private char[] _charBuffer; // Accumulate bytes for decoding into characters in a special array private int _numBytes; private byte[] _byteBuffer; // Encoding to convert chars to bytes private Encoding _encoding; private void FlushBytes() { if (_numBytes > 0) { _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); _numBytes = 0; } } internal UrlDecoder(int bufferSize, Encoding encoding) { _bufferSize = bufferSize; _encoding = encoding; _charBuffer = new char[bufferSize]; // byte buffer created on demand } internal void AddChar(char ch) { if (_numBytes > 0) FlushBytes(); _charBuffer[_numChars++] = ch; } internal void AddByte(byte b) { // if there are no pending bytes treat 7 bit bytes as characters // this optimization is temp disable as it doesn't work for some encodings /* if (_numBytes == 0 && ((b & 0x80) == 0)) { AddChar((char)b); } else */ { if (_byteBuffer == null) _byteBuffer = new byte[_bufferSize]; _byteBuffer[_numBytes++] = b; } } internal String GetString() { if (_numBytes > 0) FlushBytes(); if (_numChars > 0) return new String(_charBuffer, 0, _numChars); else return String.Empty; } } } }