// $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
}
}