// $Id$ // // Copyright 2007-2008 Cisco Systems Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. using System; using System.Collections.Generic; using System.Text; namespace Etch.Util { /// /// Models a url of the form scheme://user:password@host:port/uri;parms?terms#fragment /// public class URL { #region MEMBER VARIABLES private String _scheme; private String _user; private String _password; private String _host; private int _port; private String _uri; private List _params; private Dictionary _terms; private String _fragment; #endregion #region CONSTRUCTORS /// /// Constructs a url from a string. /// /// public URL( String urlString ) { Parse( urlString ); } /// /// Constructs a URL from another URL /// /// public URL( URL other ) { _scheme = other.Scheme; _user = other.User; _password = other.Password; _host = other.Host; _port = other.Port; _uri = other.Uri; _params = CopyParams( other._params ); _terms = CopyTerms( other._terms ); _fragment = other._fragment; } /// /// Constructs an empty URL /// public URL() { // nothing to do. } #endregion private void Parse( String s ) { // s is scheme:[//[user[:password]@]host[:port]/]uri[;params][?terms][#fragment] String[] x = StringUtil.LeftSplit( s, ':' ); if ( x == null ) throw new ArgumentNullException( "missing scheme" ); Scheme = Unescape( x[ 0 ] ); s = x[ 1 ]; // s is [//[user[:password]@]host[:port]/]uri[;params][?terms][#fragment] x = StringUtil.LeftSplit( s, '#' ); if ( x!=null ) { Fragment = Unescape( x[ 1 ] ); s = x[ 0 ]; } // s is [//[user[:password]@]host[:port]/]uri[;params][?terms] x = StringUtil.LeftSplit( s, '?' ); if ( x != null ) { ParseTerms( x[ 1 ] ); s = x[ 0 ]; } // s is [//[user[:password]@]host[:port]/]uri[;params] x = StringUtil.LeftSplit( s, ';' ); if ( x != null ) { ParseParams( x[ 1 ] ); s = x[ 0 ]; } // s is [//[user[:password]@]host[:port]/]uri if ( s.StartsWith( "//" ) ) { s = s.Substring( 2 ); // s is [user[:password]@]host[:port]/uri x = StringUtil.LeftSplit( s, '/' ); if ( x != null ) { // s is [user[:password]@]host[:port]/uri ParseHost( x[ 0 ] ); s = x[ 1 ]; } else { // s is [user[:password]@]host[:port] ParseHost( s ); s = ""; } } _uri = Unescape( s ); } private void ParseHost( String s ) { // s is [user[:password]@]host[:port] String[] x = StringUtil.LeftSplit( s, '@' ); if ( x != null ) { ParseUserPassword( x[ 0 ] ); ParseHostPort( x[ 1 ] ); } else { ParseHostPort( s ); } } private void ParseUserPassword( String s ) { // s is user[:password] String[] x = StringUtil.LeftSplit( s, ':' ); if ( x != null ) { _user = Unescape( x[ 0 ] ); _password = Unescape( x[ 1 ] ); } else { _user = Unescape( s ); } } private void ParseHostPort( String s ) { // s is host[:port] String[] x = StringUtil.LeftSplit( s, ':' ); if ( x != null ) { _host = Unescape( x[ 0 ] ); _port = Convert.ToInt32( Unescape( x[ 1 ] ) ); } else { _host = Unescape( s ); } } private void ParseParams( String s ) { // s is param[;param]* if ( s.Length == 0 ) return; EnsureParams(); String[] x; while ( ( x = StringUtil.LeftSplit( s, ';' ) ) != null ) { AddParam( Unescape( x[ 0 ] ) ); s = x[ 1 ]; } AddParam( Unescape( s ) ); } private void ParseTerms( String s ) { // s is term[&term]* if ( s.Length == 0 ) return; String[] x; while ( ( x = StringUtil.LeftSplit( s, '&' ) ) != null ) { ParseTerm( x[ 0 ] ); s = x[ 1 ]; } ParseTerm( s ); } private void ParseTerm( String s ) { // s is name[=value] if ( s.Length == 0 ) return; EnsureTerms(); String[] x = StringUtil.LeftSplit( s, '=' ); if ( x != null ) AddTerm( Unescape( x[ 0 ] ), Unescape( x[ 1 ] ) ); else AddTerm( Unescape( s ), "" ); } #region SCHEME /// /// Gets and Sets the scheme. /// Return the scheme which may be null but not blank. /// public String Scheme { get { return _scheme; } set { CheckNotBlank( "scheme", value ); _scheme = value; } } /// /// Tests the scheme for a match. The schemes are case-insensitive. /// /// a scheme to test against. /// true if the schemes match, false otherwise. public bool IsScheme( String testScheme ) { return testScheme.Equals( _scheme, StringComparison.CurrentCultureIgnoreCase ); } #endregion #region USER public String User { get { return _user; } set { CheckNotBlank( "user", value ); _user = value; } } #endregion #region PASSWORD public String Password { get { return _password; } set { CheckNotBlank( "password", value ); _password = value; } } #endregion #region HOST public String Host { get { return _host; } set { CheckNotBlank( "host", value ); _host = value; } } #endregion #region PORT public int Port { get { return _port; } set { if ( value < 0 || value > 65535 ) throw new ArgumentOutOfRangeException( "port < 0 || port > 65535 " ); _port = value; } } #endregion #region URI public String Uri { get { return _uri; } set { _uri = value; } } #endregion #region PARAMS /// /// /// /// true if there is atleast one param public bool HasParams() { return ( ( _params!=null ) && ( _params.Count > 0 ) ); } /// /// Fetches the first param found which starts with the given prefix. The /// search order is not specified, and the params are not maintained in any /// specific order. /// /// the prefix of the param to fetch (e.g., "transport="). /// the param which starts with the specified prefix. /// public String GetParam( String prefix ) { if ( _params == null ) return null; foreach ( String s in _params ) { if ( s.StartsWith( prefix ) ) return s; } return null; } /// /// /// /// an iterator over all the params. The params are strings, generally /// of the form "transport=tcp". But they can be anything you like, really. /// The iterator might be empty. /// public IEnumerator GetParams() { if ( _params == null ) return new EmptyIterator(); return _params.GetEnumerator(); } /// /// Adds a param to the set of params for this url. Only the set of unique /// params is maintained. Duplicate param values are suppressed. /// /// a param (e.g., "transport=tcp" or "01831864574898475"). /// public void AddParam( String param ) { EnsureParams(); _params.Add( param ); } /// /// Removes the first param found which starts with the given prefix. The /// search order is not specified, and the params are not maintained in any /// specific order. /// /// /// the prefix of the param to remove (e.g., "transport="). /// the param removed. /// public String RemoveParam( String prefix ) { if ( _params == null ) return null; foreach ( String s in _params ) { if ( s.StartsWith( prefix ) ) { _params.Remove( s ); return s; } } return null; } /// /// Clear all params /// public void ClearParams() { if ( _params != null ) _params.Clear(); } public void EnsureParams() { if ( _params == null ) _params = new List(); } #endregion #region QUERY TERMS /// /// /// /// true if there is at least one query term. Query terms /// are of the form name=value public Boolean HasTerms() { return _terms!=null && _terms.Count>0; } /// /// /// /// /// true if there is at least one query term with the specified /// name public Boolean HasTerm( String name ) { if ( _terms == null ) return false; return _terms.ContainsKey( name ); } /// /// /// /// /// /// if there is a query term with the specified value. public Boolean HasTerm( String name, String value ) { if ( _terms == null ) return false; Object obj = _terms[ name ]; if ( obj == null ) return false; if ( obj is List ) return ( ( List ) obj ).Contains( value ); return obj.Equals( value ); } /// /// /// /// /// /// if there is a query term with the specified value. public Boolean HasTerm( String name, int? value ) { return HasTerm( name, value.ToString() ); } /// /// /// /// /// /// if there is a query term with the specified value. public Boolean HasTerm( String name, double? value ) { return HasTerm( name, value.ToString() ); } /// /// /// /// /// true if the query term specified by name has multiple values. public Boolean HasMultipleValues(String name) { if ( _terms == null ) return false; Object obj = _terms[ name ]; if ( obj == null ) return false; if ( obj is List ) return ( ( List ) obj ).Count > 1; return false; } /// /// Gets the value of the specified query term. /// /// /// the value of the specified term, or null if not found. public String GetTerm( String name ) { Object obj = null; if ( _terms == null ) return null; try { obj = _terms[name]; } catch (Exception) { } if ( obj == null ) return null; if ( obj is List ) { IEnumerator i = ( ( List ) obj ).GetEnumerator(); i.MoveNext(); String s = i.Current; if ( i.MoveNext() ) throw new Exception( "term has multiple values" ); return s; } return ( String ) obj; } /// /// /// /// /// /// the value of the specified term, or defaultValue if not found. public String GetTerm( String name, String defaultValue ) { String value = GetTerm( name ); if ( value == null ) return defaultValue; return value; } /// /// Gets the boolean value of the specified query term. /// /// /// the boolean value, or null if not found. /// public bool? GetBooleanTerm( String name ) { String s = GetTerm( name ); if ( s == null ) return null; // modeling java behavior return s.Equals( "true", StringComparison.CurrentCultureIgnoreCase )? true : false; } public Boolean GetBooleanTerm( String name, Boolean defaultValue ) { String s = GetTerm( name ); if ( s == null ) return defaultValue; return ( s.Equals( "true", StringComparison.CurrentCultureIgnoreCase ) )? true : false; } /// /// Gets the integer value of the specified query term. /// /// /// the integer value, or null if not found. /// public int? GetIntegerTerm( String name ) { String s = GetTerm( name ); if ( s == null ) return null; return Convert.ToInt32( s ); } /// /// Gets the integer value of the specified query term. /// /// /// the value to return if the term is not found. /// the integer value, or defaultValue if not found. /// /// public int? GetIntegerTerm( String name, int defaultValue ) { int? i = GetIntegerTerm( name ); if ( i == null ) return defaultValue; return i; } public double? GetDoubleTerm( String name ) { String s = GetTerm( name ); if ( s == null ) return null; return Convert.ToDouble( s ); } /// /// Gets the values of the specified query term. /// /// /// an iterator over the string values of the query term. May be empty. public IEnumerator GetTerms( String name ) { if ( _terms == null ) return null; Object obj = _terms[ name ]; if ( obj == null ) return new EmptyIterator(); if ( obj is List ) return ( ( List ) obj ).GetEnumerator(); return new SingleIterator( ( String ) obj ); } /// /// Gets the names of the query terms. /// /// an iterator over the string names. public IEnumerator GetTermNames() { if ( _terms == null ) return new EmptyIterator(); return _terms.Keys.GetEnumerator(); } /// /// Adds the specified query term to the set of query terms. Duplicate /// name/value pairs are suppressed. /// /// /// public void AddTerm( String name, String value ) { EnsureTerms(); Object obj; try { obj = _terms[ name ]; } catch ( KeyNotFoundException ) { obj = null; } if ( obj == null ) { _terms.Add( name, value ); return; } if ( obj is List ) { ( ( List ) obj ).Add( value ); return; } List values = new List(); values.Add( ( String ) obj ); values.Add( value ); _terms[ name ] = values; } /// /// Adds the specified query term to the set of query terms. Duplicate /// name/value pairs are suppressed. /// /// /// public void AddTerm( String name, int? value ) { AddTerm( name, value.ToString() ); } /// /// Adds the specified query term to the set of query terms. Duplicate /// name/value pairs are suppressed. /// /// /// public void AddTerm( String name, double? value ) { AddTerm( name, value.ToString() ); } /// /// Removes all the values associated with the specified query term. /// /// /// true if something was removed, false otherwise. public Boolean RemoveTerm( String name ) { return _terms.Remove( name ); } /// /// Removes the specified name/value pair from the set of query terms. /// /// /// /// true if the name/value pair was found and removed. /// public Boolean RemoveTerm( String name, String value ) { if ( _terms == null ) return false; Object obj = _terms[ name ]; if ( obj == null ) return false; if ( obj is List ) { List x = ( List ) obj; Boolean ok = x.Remove( value ); if ( x.Count == 1 ) { IEnumerator it = x.GetEnumerator(); it.MoveNext(); _terms[ name ] = it.Current; } return ok; } _terms.Remove( name ); return true; } /// /// Removes all query terms /// public void ClearTerms() { if ( _terms != null ) _terms.Clear(); } private void EnsureTerms() { if ( _terms == null ) _terms = new Dictionary(); } #endregion #region FRAGMENT public String Fragment { get { return _fragment; } set { CheckNotBlank( "fragment", value ); _fragment = value; } } #endregion #region UTIL public override string ToString() { StringBuilder sb = new StringBuilder(); Escape( sb, _scheme ); sb.Append( ':' ); if ( _host != null ) { sb.Append( "//" ); if ( _user != null ) { Escape( sb, _user ); if ( _password != null ) { sb.Append( ':' ); Escape( sb, _password ); } sb.Append( '@' ); } Escape( sb, _host ); if ( _port != 0 ) { sb.Append( ':' ); sb.Append( _port ); } sb.Append( '/' ); } if ( _uri != null ) Escape( sb, _uri ); if ( HasParams() ) ParamsToString( sb ); if ( HasTerms() ) TermsToString( sb ); if ( _fragment != null ) { sb.Append( '#' ); Escape( sb, _fragment ); } return sb.ToString(); } public override int GetHashCode() { int code = 23547853; code ^= hc( _scheme ); code ^= hc( _user ); code ^= hc( _password ); code ^= hc( _host ); code ^= hc( _port ); code ^= hc( _uri ); code ^= hc( _params ); code ^= hc( _terms ); code ^= hc( _fragment ); return code; } private int hc( Dictionary m ) { return m != null ? m.GetHashCode() : 793; } private int hc( List s ) { return s != null ? s.GetHashCode() : 161; } private int hc( int? i ) { return i != null ? i.GetHashCode() : 59; } private int hc( String s ) { return s != null ? s.GetHashCode() : 91; } public override bool Equals( object obj ) { if ( obj == this ) return true; if ( !( obj is URL ) ) return false; URL other = ( URL ) obj; if ( !_scheme.Equals( other.Scheme, StringComparison.CurrentCultureIgnoreCase ) ) return false; if ( !_user.Equals( other.User ) ) return false; if ( !_password.Equals( other.Password ) ) return false; if ( !_host.Equals( other.Host ) ) return false; if ( _port != other.Port ) return false; if (!_uri.Equals(other._uri)) return false; if ( !CompareParams( _params, other._params ) ) return false; if ( !CompareTerms( _terms, other._terms ) ) return false; if ( !_fragment.Equals( other.Fragment ) ) return false; return true; } private bool CompareParams( List a, List b ) { if ( a == b ) return true; int na = a != null ? a.Count : 0; int nb = b != null ? b.Count : 0; if (na == 0 || nb == 0) return na == 0 && nb == 0; return a.Equals( b ); } private bool CompareTerms( Dictionary a, Dictionary b ) { if ( a == b ) return true; int na = a != null ? a.Count : 0; int nb = b != null ? b.Count : 0; if ( na == 0 || nb == 0 ) return na == 0 && nb == 0; return a.Equals( b ); } private void ParamsToString( StringBuilder sb ) { IEnumerator it = GetParams(); while ( it.MoveNext() ) { String param = it.Current; sb.Append( ';' ); Escape( sb, param ); } } private void TermsToString( StringBuilder sb ) { char sep = '?'; IEnumerator it = GetTermNames(); while ( it.MoveNext() ) { String name = it.Current; IEnumerator jt = GetTerms( name ); while ( jt.MoveNext() ) { String value = jt.Current; sb.Append( sep ); Escape( sb, name ); sb.Append( '=' ); Escape( sb, value ); sep = '&'; } } } /// /// Dumps URL contents for easy viewing /// public void Dump() { Console.WriteLine( "---------------" ); Console.WriteLine( "scheme = "+_scheme ); Console.WriteLine( "user = "+_user ); Console.WriteLine( "password = "+_password ); Console.WriteLine( "host = "+_host ); Console.WriteLine( "port = "+_port ); Console.WriteLine( "uri = "+_uri ); Console.WriteLine( "params = "+_params ); Console.WriteLine( "terms = "+_terms ); Console.WriteLine( "fragment = "+_fragment ); } private void Escape( StringBuilder sb, String s ) { CharIterator i = new CharIterator( s ); while ( i.MoveNext() ) { char c = i.Current; if ( IsEscaped( c ) ) { sb.Append( '%' ); sb.Append( StringUtil.ToHex( ( c >> 4 )& 15 ) ); sb.Append( StringUtil.ToHex( c & 15 ) ); } else if ( c == ' ' ) { sb.Append( '+' ); } else { sb.Append( c ); } } } private static Boolean IsEscaped( char c ) { if ( c >= '0' && c <= '9' ) return false; if ( c >= 'A' && c <= 'Z' ) return false; if ( c >= 'a' && c <= 'z' ) return false; if ( c == ' ' ) return false; if ( c == ',' ) return false; if ( c == '.' ) return false; if ( c == '/' ) return false; if ( c == '!' ) return false; if ( c == '$' ) return false; if ( c == '^' ) return false; if ( c == '*' ) return false; if ( c == '(' ) return false; if ( c == ')' ) return false; if ( c == '_' ) return false; if ( c == '-' ) return false; if ( c == '{' ) return false; if ( c == '}' ) return false; if ( c == '[' ) return false; if ( c == ']' ) return false; if ( c == '|' ) return false; if ( c == '\\' ) return false; return true; } private String Unescape( String s ) { StringBuilder sb = new StringBuilder(); CharIterator i = new CharIterator( s ); while ( i.MoveNext() ) { char c = i.Current; if ( c == '%') { i.MoveNext(); char c1 = i.Current; i.MoveNext(); char c2 = i.Current; sb.Append( ( char ) ( ( StringUtil.FromHex( c1 ) << 4 ) | StringUtil.FromHex( c2 ) ) ); } else if ( c == '+' ) { sb.Append( ' ' ); } else { sb.Append( c ); } } return sb.ToString(); } private static void CheckNotBlank( String name, String value ) { if ( value != null && value.Length == 0 ) throw new ArgumentException( name + " is blank" ); } private static String ValueToString( double value ) { return Convert.ToString( value ); } private static String ValueToString( int value ) { return Convert.ToString( value ); } private static List CopyParams( List paramss ) { if ( paramss == null ) return null; return new List( paramss ); } private static Dictionary CopyTerms( Dictionary terms ) { if ( terms == null ) return null; Dictionary map = new Dictionary( terms ); IEnumerator> i = map.GetEnumerator(); while ( i.MoveNext() ) { KeyValuePair me = i.Current; Object obj = me.Value; if ( obj is List ) map[ me.Key ] = new List( ( List ) obj ); } return map; } #endregion } }