using System; using System.Collections.Specialized; using System.Net; using EmbedIO.Internal; namespace EmbedIO.Utilities { /// /// Parses URL queries or URL-encoded HTML forms. /// public static class UrlEncodedDataParser { /// /// Parses a URL query or URL-encoded HTML form. /// Unlike , the returned /// will have bracketed indexes stripped away; /// for example, a[0]=1&a[1]=2 will yield the same result as a=1&a=2, /// i.e. a with one key (a) associated with /// two values (1 and 2). /// /// The string to parse. /// If this parameter is , /// tokens not followed by an equal sign (e.g. this in a=1&this&b=2) /// will be grouped as values of a null key. /// This is the same behavior as the and /// properties. /// If this parameter is , tokens not followed by an equal sign /// (e.g. this in a=1&this&b=2) will be considered keys with an empty /// value. This is the same behavior as the /// extension method. /// (the default) to return /// a mutable (non-read-only) collection; to return a read-only collection. /// A containing the parsed data. public static NameValueCollection Parse(string source, bool groupFlags, bool mutableResult = true) { var result = new LockableNameValueCollection(); // Verify there is data to parse; otherwise, return an empty collection. if (string.IsNullOrEmpty(source)) { if (!mutableResult) result.MakeReadOnly(); return result; } void AddKeyValuePair(string? key, string value) { if (key != null) { // Decode the key. key = WebUtility.UrlDecode(key); // Discard bracketed index (used e.g. by PHP) var bracketPos = key.IndexOf("[", StringComparison.Ordinal); if (bracketPos > 0) key = key.Substring(0, bracketPos); } // Decode the value. value = WebUtility.UrlDecode(value); // Add the KVP to the collection. result.Add(key, value); } // Skip the initial question mark, // in case source is the Query property of a Uri. var kvpPos = source[0] == '?' ? 1 : 0; var length = source.Length; while (kvpPos < length) { var separatorPos = kvpPos; var equalPos = -1; while (separatorPos < length) { var c = source[separatorPos]; if (c == '&') break; if (c == '=' && equalPos < 0) equalPos = separatorPos; separatorPos++; } // Split by the equals char into key and value. // Some KVPS will have only their key, some will have both key and value // Some other might be repeated which really means an array if (equalPos < 0) { if (groupFlags) { AddKeyValuePair(null, source.Substring(kvpPos, separatorPos - kvpPos)); } else { AddKeyValuePair(source.Substring(kvpPos, separatorPos - kvpPos), string.Empty); } } else { AddKeyValuePair( source.Substring(kvpPos, equalPos - kvpPos), source.Substring(equalPos + 1, separatorPos - equalPos - 1)); } // Edge case: if the last character in source is '&', // there's an empty KVP that we would otherwise skip. if (separatorPos == length - 1) { AddKeyValuePair(groupFlags ? null : string.Empty, string.Empty); break; } // On to next KVP kvpPos = separatorPos + 1; } if (!mutableResult) result.MakeReadOnly(); return result; } } }