//------------------------------------------------------------------------------
// <copyright file="httpserverutility.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

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;

    //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility"]/*' />
    /// <devdoc>
    /// </devdoc>
    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
        //

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncode"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string UrlEncode(string str) {
            if (str == null)
                return null;
            return UrlEncode(str, Encoding.UTF8);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlPathEncode"]/*' />
        /// <devdoc>
        ///    <para>
        ///       URL encodes a path portion of a URL string and returns the encoded string.
        ///    </para>
        /// </devdoc>
        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;
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncode1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        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;
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncode2"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string UrlEncode(byte[] bytes) {
            if (bytes == null)
                return null;
            return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes));
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncode3"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string UrlEncode(byte[] bytes, int offset, int count) {
            if (bytes == null)
                return null;
            return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, offset, count));
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncodeToBytes"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static byte[] UrlEncodeToBytes(string str) {
            if (str == null)
                return null;
            return UrlEncodeToBytes(str, Encoding.UTF8);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncodeToBytes1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        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);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncodeToBytes2"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static byte[] UrlEncodeToBytes(byte[] bytes) {
            if (bytes == null)
                return null;
            return UrlEncodeToBytes(bytes, 0, bytes.Length);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncodeToBytes3"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        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);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncodeUnicode"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string UrlEncodeUnicode(string str) {
            if (str == null)
                return null;
            return UrlEncodeUnicodeStringToStringInternal(str, false);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlEncodeUnicodeToBytes"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static byte[] UrlEncodeUnicodeToBytes(string str) {
            if (str == null)
                return null;
            return Encoding.ASCII.GetBytes(UrlEncodeUnicode(str));
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecode"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string UrlDecode(string str) {
            if (str == null)
                return null;
            return UrlDecode(str, Encoding.UTF8);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecode1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string UrlDecode(string str, Encoding e) {
            if (str == null)
                return null;
            return UrlDecodeStringFromStringInternal(str, e);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecode2"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string UrlDecode(byte[] bytes, Encoding e) {
            if (bytes == null)
                return null;
            return UrlDecode(bytes, 0, bytes.Length, e);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecode3"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        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);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecodeToBytes"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static byte[] UrlDecodeToBytes(string str) {
            if (str == null)
                return null;
            return UrlDecodeToBytes(str, Encoding.UTF8);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecodeToBytes1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static byte[] UrlDecodeToBytes(string str, Encoding e) {
            if (str == null)
                return null;
            return UrlDecodeToBytes(e.GetBytes(str));
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecodeToBytes2"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static byte[] UrlDecodeToBytes(byte[] bytes) {
            if (bytes == null)
                return null;
            return UrlDecodeToBytes(bytes, 0, (bytes != null) ? bytes.Length : 0);
        }

        //| <include file='doc\httpserverutility.uex' path='docs/doc[@for="HttpUtility.UrlDecodeToBytes3"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        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;
        }
    }
}
