1 //------------------------------------------------------------------------------
2 // <copyright file="HttpEncoder.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 * Base class providing extensibility hooks for custom encoding / decoding
10 * Copyright (c) 2009 Microsoft Corporation
13 namespace System.Web.Util {
15 using System.Diagnostics.CodeAnalysis;
16 using System.Globalization;
21 using System.Web.Configuration;
23 public class HttpEncoder {
25 private static HttpEncoder _customEncoder;
26 private readonly bool _isDefaultEncoder;
28 private static readonly Lazy<HttpEncoder> _customEncoderResolver =
29 new Lazy<HttpEncoder>(GetCustomEncoderFromConfig);
31 private static readonly HttpEncoder _defaultEncoder = new HttpEncoder();
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"
40 public HttpEncoder() {
41 _isDefaultEncoder = (GetType() == typeof(HttpEncoder));
44 public static HttpEncoder Current {
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
49 HttpContext httpContext = HttpContext.Current;
50 if (httpContext != null && httpContext.DisableCustomHttpEncoder) {
51 return _defaultEncoder;
54 if (_customEncoder == null) {
55 _customEncoder = _customEncoderResolver.Value;
57 return _customEncoder;
62 throw new ArgumentNullException("value");
64 _customEncoder = value;
68 public static HttpEncoder Default {
70 return _defaultEncoder;
74 internal virtual bool JavaScriptEncodeAmpersand {
76 return !AppSettings.JavaScriptDoNotEncodeAmpersand;
80 private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c) {
81 builder.Append("\\u");
82 builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
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
89 || c == '\'' // HTML-sensitive chars encoded for safety
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)
98 internal static string CollapsePercentUFromStringInternal(string s, Encoding e) {
100 UrlDecoder helper = new UrlDecoder(count, e);
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);
109 for (int pos = 0; pos < count; pos++) {
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]);
119 if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { //valid 4 hex chars
120 ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
129 if ((ch & 0xFF80) == 0)
130 helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
134 return Utf16StringValidator.ValidateString(helper.GetString());
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;
144 Type encoderType = ConfigUtil.GetType(encoderTypeName, "encoderType", runtimeSection);
145 ConfigUtil.CheckBaseType(typeof(HttpEncoder) /* expectedBaseType */, encoderType, "encoderType", runtimeSection);
148 HttpEncoder encoder = (HttpEncoder)HttpRuntime.CreatePublicInstance(encoderType);
152 // Encode the header if it contains a CRLF pair
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]);
175 sanitizedHeader = sb.ToString();
178 return sanitizedHeader;
181 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters",
182 Justification = "Input parameter strings are immutable, so this is an appropriate way to return multiple strings.")]
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);
188 // Returns true if the string contains a control character (other than horizontal tab) or the DEL character.
189 private static bool HeaderValueNeedsEncoding(string value) {
190 foreach (char c in value) {
191 if ((c < 32 && c != 9) || (c == 127)) {
198 internal string HtmlAttributeEncode(string value) {
199 if (String.IsNullOrEmpty(value)) {
203 if(_isDefaultEncoder) {
204 // Don't create string writer if we don't have nothing to encode
205 int pos = IndexOfHtmlAttributeEncodingChars(value, 0);
211 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
212 HtmlAttributeEncode(value, writer);
213 return writer.ToString();
216 protected internal virtual void HtmlAttributeEncode(string value, TextWriter output) {
220 if (output == null) {
221 throw new ArgumentNullException("output");
224 // we have a special faster path for HttpWriter
225 HttpWriter httpWriter = output as HttpWriter;
226 if (httpWriter != null) {
227 HtmlAttributeEncodeInternal(value, httpWriter);
230 HtmlAttributeEncodeInternal(value, output);
234 private static void HtmlAttributeEncodeInternal(string value, HttpWriter writer) {
235 int pos = IndexOfHtmlAttributeEncodingChars(value, 0);
241 int cch = value.Length;
244 if (pos > startPos) {
245 writer.WriteString(value, startPos, pos - startPos);
248 char ch = value[pos];
251 writer.Write(""");
254 writer.Write("'");
257 writer.Write("&");
260 // Whidbey 32404: The character '<' is not valid in an XML attribute value.
261 // (See the W3C XML rec).
262 writer.Write("<");
270 pos = IndexOfHtmlAttributeEncodingChars(value, startPos);
272 writer.WriteString(value, startPos, cch - startPos);
278 private unsafe static void HtmlAttributeEncodeInternal(String s, TextWriter output) {
279 int index = IndexOfHtmlAttributeEncodingChars(s, 0);
284 int cch = s.Length - index;
285 fixed (char* str = s) {
287 while (index-- > 0) {
288 output.Write(*pch++);
296 output.Write("<");
299 output.Write(""");
302 output.Write("'");
305 output.Write("&");
320 internal string HtmlDecode(string value) {
321 if (String.IsNullOrEmpty(value))
326 if(_isDefaultEncoder) {
327 return WebUtility.HtmlDecode(value);
330 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
331 HtmlDecode(value, writer);
332 return writer.ToString();
335 protected internal virtual void HtmlDecode(string value, TextWriter output) {
336 WebUtility.HtmlDecode(value, output);
339 internal string HtmlEncode(string value) {
340 if (String.IsNullOrEmpty(value))
345 if(_isDefaultEncoder) {
346 return WebUtility.HtmlEncode(value);
349 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
350 HtmlEncode(value, writer);
351 return writer.ToString();
354 protected internal virtual void HtmlEncode(string value, TextWriter output) {
355 WebUtility.HtmlEncode(value, output);
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--) {
370 return s.Length - cch;
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.
384 HttpEncoder encoder = _customEncoderResolver.Value;
387 private static bool IsNonAsciiByte(byte b) {
388 return (b >= 0x7F || b < 0x20);
391 protected internal virtual string JavaScriptStringEncode(string value) {
392 if (String.IsNullOrEmpty(value)) {
396 StringBuilder b = null;
399 for (int i = 0; i < value.Length; i++) {
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)) {
406 b = new StringBuilder(value.Length + 5);
410 b.Append(value, startIndex, count);
440 if (CharRequiresJavaScriptEncoding(c)) {
441 AppendCharAsUnicodeJavaScript(b, c);
455 b.Append(value, startIndex, count);
461 internal byte[] UrlDecode(byte[] bytes, int offset, int count) {
462 if (!ValidateUrlEncodingParameters(bytes, offset, count)) {
466 int decodedBytesCount = 0;
467 byte[] decodedBytes = new byte[count];
469 for (int i = 0; i < count; i++) {
470 int pos = offset + i;
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]);
480 if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars
481 b = (byte)((h1 << 4) | h2);
486 decodedBytes[decodedBytesCount++] = b;
489 if (decodedBytesCount < decodedBytes.Length) {
490 byte[] newDecodedBytes = new byte[decodedBytesCount];
491 Array.Copy(decodedBytes, newDecodedBytes, decodedBytesCount);
492 decodedBytes = newDecodedBytes;
498 internal string UrlDecode(byte[] bytes, int offset, int count, Encoding encoding) {
499 if (!ValidateUrlEncodingParameters(bytes, offset, count)) {
503 UrlDecoder helper = new UrlDecoder(count, encoding);
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
509 for (int i = 0; i < count; i++) {
510 int pos = offset + i;
513 // The code assumes that + and % cannot be in multibyte sequence
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]);
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);
535 int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]);
536 int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]);
538 if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars
539 b = (byte)((h1 << 4) | h2);
548 return Utf16StringValidator.ValidateString(helper.GetString());
551 internal string UrlDecode(string value, Encoding encoding) {
556 int count = value.Length;
557 UrlDecoder helper = new UrlDecoder(count, encoding);
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
563 for (int pos = 0; pos < count; pos++) {
564 char ch = value[pos];
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]);
576 if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { // valid 4 hex chars
577 ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
586 int h1 = HttpEncoderUtility.HexToInt(value[pos + 1]);
587 int h2 = HttpEncoderUtility.HexToInt(value[pos + 2]);
589 if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars
590 byte b = (byte)((h1 << 4) | h2);
600 if ((ch & 0xFF80) == 0)
601 helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
606 return Utf16StringValidator.ValidateString(helper.GetString());
609 internal byte[] UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) {
610 byte[] encoded = UrlEncode(bytes, offset, count);
612 return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes))
613 ? (byte[])encoded.Clone()
617 protected internal virtual byte[] UrlEncode(byte[] bytes, int offset, int count) {
618 if (!ValidateUrlEncodingParameters(bytes, offset, count)) {
626 for (int i = 0; i < count; i++) {
627 char ch = (char)bytes[offset + i];
631 else if (!HttpEncoderUtility.IsUrlSafeChar(ch))
635 // nothing to expand?
636 if (cSpaces == 0 && cUnsafe == 0) {
637 // DevDiv 912606: respect "offset" and "count"
638 if (0 == offset && bytes.Length == count) {
642 var subarray = new byte[count];
643 Buffer.BlockCopy(bytes, offset, subarray, 0, count);
648 // expand not 'safe' characters into %XX, spaces to +s
649 byte[] expandedBytes = new byte[count + cUnsafe * 2];
652 for (int i = 0; i < count; i++) {
653 byte b = bytes[offset + i];
656 if (HttpEncoderUtility.IsUrlSafeChar(ch)) {
657 expandedBytes[pos++] = b;
659 else if (ch == ' ') {
660 expandedBytes[pos++] = (byte)'+';
663 expandedBytes[pos++] = (byte)'%';
664 expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf);
665 expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f);
669 return expandedBytes;
672 // Helper to encode the non-ASCII url characters only
673 internal String UrlEncodeNonAscii(string str, Encoding e) {
674 if (String.IsNullOrEmpty(str))
678 byte[] bytes = e.GetBytes(str);
679 byte[] encodedBytes = UrlEncodeNonAscii(bytes, 0, bytes.Length, false /* alwaysCreateNewReturnValue */);
680 return Encoding.ASCII.GetString(encodedBytes);
683 internal byte[] UrlEncodeNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) {
684 if (!ValidateUrlEncodingParameters(bytes, offset, count)) {
691 for (int i = 0; i < count; i++) {
692 if (IsNonAsciiByte(bytes[offset + i]))
696 // nothing to expand?
697 if (!alwaysCreateNewReturnValue && cNonAscii == 0)
700 // expand not 'safe' characters into %XX, spaces to +s
701 byte[] expandedBytes = new byte[count + cNonAscii * 2];
704 for (int i = 0; i < count; i++) {
705 byte b = bytes[offset + i];
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);
713 expandedBytes[pos++] = b;
717 return expandedBytes;
720 [Obsolete("This method produces non-standards-compliant output and has interoperability issues. The preferred alternative is UrlEncode(*).")]
721 internal string UrlEncodeUnicode(string value, bool ignoreAscii) {
726 int l = value.Length;
727 StringBuilder sb = new StringBuilder(l);
729 for (int i = 0; i < l; i++) {
732 if ((ch & 0xff80) == 0) { // 7 bit?
733 if (ignoreAscii || HttpEncoderUtility.IsUrlSafeChar(ch)) {
736 else if (ch == ' ') {
741 sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf));
742 sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf));
745 else { // arbitrary Unicode?
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));
754 return sb.ToString();
757 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings",
758 Justification = "Does not represent an entire URL, just a portion.")]
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)) {
766 string schemeAndAuthority;
768 string queryAndFragment;
769 bool isValidUrl = UriUtil.TrySplitUriForPathEncode(value, out schemeAndAuthority, out path, out queryAndFragment, checkScheme: false);
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;
777 queryAndFragment = null;
780 return schemeAndAuthority + UrlPathEncodeImpl(path) + queryAndFragment;
783 return UrlPathEncodeImpl(value);
787 // This is the original UrlPathEncode(string)
788 [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings",
789 Justification = "Does not represent an entire URL, just a portion.")]
790 private string UrlPathEncodeImpl(string value) {
791 if (String.IsNullOrEmpty(value)) {
795 // recurse in case there is a query string
796 int i = value.IndexOf('?');
798 return UrlPathEncodeImpl(value.Substring(0, i)) + value.Substring(i);
800 // encode DBCS characters and spaces only
801 return HttpEncoderUtility.UrlEncodeSpaces(UrlEncodeNonAscii(value, Encoding.UTF8));
804 internal byte[] UrlTokenDecode(string input) {
806 throw new ArgumentNullException("input");
808 int len = input.Length;
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)
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];
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];
833 base64Chars[iter] = '+';
837 base64Chars[iter] = '/';
841 base64Chars[iter] = c;
846 ////////////////////////////////////////////////////////
847 // Step 4: Add padding chars
848 for (int iter = len - 1; iter < base64Chars.Length; iter++) {
849 base64Chars[iter] = '=';
852 // Do the actual conversion
853 return Convert.FromBase64CharArray(base64Chars, 0, base64Chars.Length);
856 internal string UrlTokenEncode(byte[] input) {
858 throw new ArgumentNullException("input");
859 if (input.Length < 1)
862 string base64Str = null;
864 char[] base64Chars = null;
866 ////////////////////////////////////////////////////////
867 // Step 1: Do a Base64 encoding
868 base64Str = Convert.ToBase64String(input);
869 if (base64Str == null)
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!
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
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];
894 base64Chars[iter] = '-';
898 base64Chars[iter] = '_';
903 base64Chars[iter] = c;
907 base64Chars[iter] = c;
911 return new string(base64Chars);
914 internal static bool ValidateUrlEncodingParameters(byte[] bytes, int offset, int count) {
915 if (bytes == null && count == 0)
918 throw new ArgumentNullException("bytes");
920 if (offset < 0 || offset > bytes.Length) {
921 throw new ArgumentOutOfRangeException("offset");
923 if (count < 0 || offset + count > bytes.Length) {
924 throw new ArgumentOutOfRangeException("count");
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;
934 // Accumulate characters in a special array
935 private int _numChars;
936 private char[] _charBuffer;
938 // Accumulate bytes for decoding into characters in a special array
939 private int _numBytes;
940 private byte[] _byteBuffer;
942 // Encoding to convert chars to bytes
943 private Encoding _encoding;
945 private void FlushBytes() {
947 _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
952 internal UrlDecoder(int bufferSize, Encoding encoding) {
953 _bufferSize = bufferSize;
954 _encoding = encoding;
956 _charBuffer = new char[bufferSize];
957 // byte buffer created on demand
960 internal void AddChar(char ch) {
964 _charBuffer[_numChars++] = ch;
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
971 if (_numBytes == 0 && ((b & 0x80) == 0)) {
977 if (_byteBuffer == null)
978 _byteBuffer = new byte[_bufferSize];
980 _byteBuffer[_numBytes++] = b;
984 internal String GetString() {
989 return new String(_charBuffer, 0, _numChars);