//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web { using System.Text; using System.Threading; using System.IO; using System.Collections; using System.Globalization; using System.Web.Hosting; using System.Diagnostics; //| /// /// public sealed class HttpUtility { // // NOTE: Various entire chunks of functionality are not included in // this port. In fact, only URL encoding/decoding is available. // ////////////////////////////////////////////////////////////////////////// // // URL decoding / encoding // ////////////////////////////////////////////////////////////////////////// // // Public static methods // //| /// /// [To be supplied.] /// public static string UrlEncode(string str) { if (str == null) return null; return UrlEncode(str, Encoding.UTF8); } //| /// /// /// URL encodes a path portion of a URL string and returns the encoded string. /// /// public static string UrlPathEncode(string str) { if (str == null) return null; // recurse in case there is a query string int i = str.IndexOf('?'); if (i >= 0) return UrlPathEncode(str.Substring(0, i)) + str.Substring(i); // encode DBCS characters and spaces only return UrlEncodeSpaces(UrlEncodeNonAscii(str, Encoding.UTF8)); } internal static string AspCompatUrlEncode(string s) { s = UrlEncode(s); s = s.Replace("!", "%21"); s = s.Replace("*", "%2A"); s = s.Replace("(", "%28"); s = s.Replace(")", "%29"); s = s.Replace("-", "%2D"); s = s.Replace(".", "%2E"); s = s.Replace("_", "%5F"); s = s.Replace("\\", "%5C"); return s; } //| /// /// [To be supplied.] /// public static string UrlEncode(string str, Encoding e) { if (str == null) return null; return Encoding.ASCII.GetString(UrlEncodeToBytes(str, e)); } // Helper to encode the non-ASCII url characters only internal static String UrlEncodeNonAscii(string str, Encoding e) { if (str == null || str.Length == 0) return str; if (e == null) e = Encoding.UTF8; byte[] bytes = e.GetBytes(str); bytes = UrlEncodeBytesToBytesInternalNonAscii(bytes, 0, bytes.Length, false); return Encoding.ASCII.GetString(bytes); } // Helper to encode spaces only internal static String UrlEncodeSpaces(string str) { if (str != null && str.IndexOf(' ') >= 0) str = str.Replace(" ", "%20"); return str; } //| /// /// [To be supplied.] /// public static string UrlEncode(byte[] bytes) { if (bytes == null) return null; return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes)); } //| /// /// [To be supplied.] /// public static string UrlEncode(byte[] bytes, int offset, int count) { if (bytes == null) return null; return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, offset, count)); } //| /// /// [To be supplied.] /// public static byte[] UrlEncodeToBytes(string str) { if (str == null) return null; return UrlEncodeToBytes(str, Encoding.UTF8); } //| /// /// [To be supplied.] /// public static byte[] UrlEncodeToBytes(string str, Encoding e) { if (str == null) return null; byte[] bytes = e.GetBytes(str); return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false); } //| /// /// [To be supplied.] /// public static byte[] UrlEncodeToBytes(byte[] bytes) { if (bytes == null) return null; return UrlEncodeToBytes(bytes, 0, bytes.Length); } //| /// /// [To be supplied.] /// public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) { if (bytes == null && count == 0) return null; 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 UrlEncodeBytesToBytesInternal(bytes, offset, count, true); } //| /// /// [To be supplied.] /// public static string UrlEncodeUnicode(string str) { if (str == null) return null; return UrlEncodeUnicodeStringToStringInternal(str, false); } //| /// /// [To be supplied.] /// public static byte[] UrlEncodeUnicodeToBytes(string str) { if (str == null) return null; return Encoding.ASCII.GetBytes(UrlEncodeUnicode(str)); } //| /// /// [To be supplied.] /// public static string UrlDecode(string str) { if (str == null) return null; return UrlDecode(str, Encoding.UTF8); } //| /// /// [To be supplied.] /// public static string UrlDecode(string str, Encoding e) { if (str == null) return null; return UrlDecodeStringFromStringInternal(str, e); } //| /// /// [To be supplied.] /// public static string UrlDecode(byte[] bytes, Encoding e) { if (bytes == null) return null; return UrlDecode(bytes, 0, bytes.Length, e); } //| /// /// [To be supplied.] /// public static string UrlDecode(byte[] bytes, int offset, int count, Encoding e) { if (bytes == null && count == 0) return null; 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 UrlDecodeStringFromBytesInternal(bytes, offset, count, e); } //| /// /// [To be supplied.] /// public static byte[] UrlDecodeToBytes(string str) { if (str == null) return null; return UrlDecodeToBytes(str, Encoding.UTF8); } //| /// /// [To be supplied.] /// public static byte[] UrlDecodeToBytes(string str, Encoding e) { if (str == null) return null; return UrlDecodeToBytes(e.GetBytes(str)); } //| /// /// [To be supplied.] /// public static byte[] UrlDecodeToBytes(byte[] bytes) { if (bytes == null) return null; return UrlDecodeToBytes(bytes, 0, (bytes != null) ? bytes.Length : 0); } //| /// /// [To be supplied.] /// public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count) { if (bytes == null && count == 0) return null; 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 UrlDecodeBytesFromBytesInternal(bytes, offset, count); } // // Implementation for encoding // private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue) { 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 (!IsSafe(ch)) cUnsafe++; } // nothing to expand? if (!alwaysCreateReturnValue && cSpaces == 0 && cUnsafe == 0) return bytes; // 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 (IsSafe(ch)) { expandedBytes[pos++] = b; } else if (ch == ' ') { expandedBytes[pos++] = (byte)'+'; } else { expandedBytes[pos++] = (byte)'%'; expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); } } return expandedBytes; } private static byte[] UrlEncodeBytesToBytesInternalNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue) { int cNonAscii = 0; // count them first for (int i = 0; i < count; i++) { if ((bytes[offset + i] & 0x80) != 0) cNonAscii++; } // nothing to expand? if (!alwaysCreateReturnValue && 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 ((bytes[offset + i] & 0x80) == 0) { expandedBytes[pos++] = b; } else { expandedBytes[pos++] = (byte)'%'; expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); } } return expandedBytes; } private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii) { int l = s.Length; StringBuilder sb = new StringBuilder(l); for (int i = 0; i < l; i++) { char ch = s[i]; if ((ch & 0xff80) == 0) { // 7 bit? if (ignoreAscii || IsSafe(ch)) { sb.Append(ch); } else if (ch == ' ') { sb.Append('+'); } else { sb.Append('%'); sb.Append(IntToHex((ch >> 4) & 0xf)); sb.Append(IntToHex((ch ) & 0xf)); } } else { // arbitrary Unicode? sb.Append("%u"); sb.Append(IntToHex((ch >> 12) & 0xf)); sb.Append(IntToHex((ch >> 8) & 0xf)); sb.Append(IntToHex((ch >> 4) & 0xf)); sb.Append(IntToHex((ch ) & 0xf)); } } return sb.ToString(); } // // Implementation for decoding // // 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; } } internal static string CollapsePercentUFromStringInternal(string s, Encoding e) { int count = s.Length; UrlDecoder helper = new UrlDecoder(count, e); // go through the string's chars collapsing just %uXXXX and // appending each char as char int loc = s.IndexOf("%u"); 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 = HexToInt(s[pos+2]); int h2 = HexToInt(s[pos+3]); int h3 = HexToInt(s[pos+4]); int h4 = 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 helper.GetString(); } private static string UrlDecodeStringFromStringInternal(string s, Encoding e) { int count = s.Length; UrlDecoder helper = new UrlDecoder(count, e); // 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 = s[pos]; if (ch == '+') { ch = ' '; } else if (ch == '%' && pos < count - 2) { if (s[pos + 1] == 'u' && pos < count - 5) { int h1 = HexToInt(s[pos+2]); int h2 = HexToInt(s[pos+3]); int h3 = HexToInt(s[pos+4]); int h4 = 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; // only add as char helper.AddChar(ch); continue; } } else { int h1 = HexToInt(s[pos+1]); int h2 = HexToInt(s[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 helper.GetString(); } private static string UrlDecodeStringFromBytesInternal(byte[] buf, int offset, int count, Encoding e) { UrlDecoder helper = new UrlDecoder(count, e); // 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 = buf[pos]; // The code assumes that + and % cannot be in multibyte sequence if (b == '+') { b = (byte)' '; } else if (b == '%' && i < count - 2) { if (buf[pos + 1] == 'u' && i < count - 5) { int h1 = HexToInt((char)buf[pos+2]); int h2 = HexToInt((char)buf[pos+3]); int h3 = HexToInt((char)buf[pos+4]); int h4 = HexToInt((char)buf[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 = HexToInt((char)buf[pos+1]); int h2 = HexToInt((char)buf[pos+2]); if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars b = (byte)((h1 << 4) | h2); i += 2; } } } helper.AddByte(b); } return helper.GetString(); } private static byte[] UrlDecodeBytesFromBytesInternal(byte[] buf, int offset, int count) { int decodedBytesCount = 0; byte[] decodedBytes = new byte[count]; for (int i = 0; i < count; i++) { int pos = offset + i; byte b = buf[pos]; if (b == '+') { b = (byte)' '; } else if (b == '%' && i < count - 2) { int h1 = HexToInt((char)buf[pos+1]); int h2 = HexToInt((char)buf[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; } // // Private helpers for URL encoding/decoding // private static int HexToInt(char h) { return( h >= '0' && h <= '9' ) ? h - '0' : ( h >= 'a' && h <= 'f' ) ? h - 'a' + 10 : ( h >= 'A' && h <= 'F' ) ? h - 'A' + 10 : -1; } private static char IntToHex(int n) { Debug.Assert(n < 0x10); if (n <= 9) return(char)(n + (int)'0'); else return(char)(n - 10 + (int)'a'); } // Set of safe chars, from RFC 1738.4 minus '+' private static bool IsSafe(char ch) { if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') return true; switch (ch) { case '-': case '_': case '.': case '!': case '*': case '\'': case '(': case ')': return true; } return false; } } }