You are viewing a plain text version of this content. The canonical link for it is here.
Posted to m2-dev@maven.apache.org by ev...@apache.org on 2004/06/25 11:37:30 UTC

cvs commit: maven-components/maven-mboot/src/main Base64.java HttpUtils.java

evenisse    2004/06/25 02:37:30

  Modified:    maven-mboot/src/main HttpUtils.java
  Added:       maven-mboot/src/main Base64.java
  Log:
  Upgrade HttpUtils from maven1
  
  Revision  Changes    Path
  1.3       +181 -44   maven-components/maven-mboot/src/main/HttpUtils.java
  
  Index: HttpUtils.java
  ===================================================================
  RCS file: /home/cvs/maven-components/maven-mboot/src/main/HttpUtils.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- HttpUtils.java	27 Jan 2004 18:58:11 -0000	1.2
  +++ HttpUtils.java	25 Jun 2004 09:37:30 -0000	1.3
  @@ -1,4 +1,22 @@
  +/* ====================================================================
  + *   Copyright 2001-2004 The Apache Software Foundation.
  + *
  + *   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.
  + * ====================================================================
  + */
  +
   import java.io.File;
  +import java.io.FileNotFoundException;
   import java.io.FileOutputStream;
   import java.io.IOException;
   import java.io.InputStream;
  @@ -8,8 +26,31 @@
   import java.net.URL;
   import java.net.URLConnection;
   
  +/**
  + * Http utils for retrieving files.
  + *
  + * @author costin@dnt.ro
  + * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth)
  + * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
  + *
  + * @todo Need to add a timeout so we can flip to a backup repository.
  + * @todo Download everything in a single session.
  + * @todo Throw meaningful exception when authentication fails.
  + */
   public class HttpUtils
   {
  +    /**
  +     * Use a proxy to bypass the firewall with or without authentication
  +     *
  +     * @param proxyHost Proxy Host (if proxy is required), or null
  +     * @param proxyPort Proxy Port (if proxy is required), or null
  +     * @param proxyUserName Proxy Username (if authentification is required),
  +     *        or null
  +     * @param proxyPassword Proxy Password (if authentification is required),
  +     *        or null
  +     * @throws SecurityException if an operation is not authorized by the
  +     * SecurityManager
  +     */
       public static void useProxyUser( final String proxyHost,
                                        final String proxyPort,
                                        final String proxyUserName,
  @@ -35,6 +76,26 @@
           }
       }
   
  +    /**
  +     * Retrieve a remote file.  Throws an Exception on errors unless the 
  +     * ifnoreErrors flag is set to True
  +     *
  +     * @param url the of the file to retrieve
  +     * @param destinationFile where to store it
  +     * @param ignoreErrors whether to ignore errors during I/O or throw an
  +     *      exception when they happen
  +     * @param useTimestamp whether to check the modified timestamp on the
  +     *      <code>destinationFile</code> against the remote <code>source</code>
  +     * @param proxyHost Proxy Host (if proxy is required), or null
  +     * @param proxyPort Proxy Port (if proxy is required), or null
  +     * @param proxyUserName Proxy Username (if authentification is required),
  +     *        or null.
  +     * @param proxyPassword Proxy Password (if authentification is required),
  +     *        or null.
  +     * @param useChecksum Flag to indicate the use of the checksum for the retrieved
  +     *        artifact if it is available.
  +     * @throws IOException If an I/O exception occurs.
  +     */
       public static void getFile( String url,
                                   File destinationFile,
                                   boolean ignoreErrors,
  @@ -44,7 +105,7 @@
                                   String proxyUserName,
                                   String proxyPassword,
                                   boolean useChecksum )
  -        throws Exception
  +        throws IOException
       {
           // Get the requested file.
           getFile( url,
  @@ -80,6 +141,24 @@
           }
       }
   
  +    /**
  +     * Retrieve a remote file.  Throws an Exception on errors unless the 
  +     * ifnoreErrors flag is set to True
  +     *
  +     * @param url the of the file to retrieve
  +     * @param destinationFile where to store it
  +     * @param ignoreErrors whether to ignore errors during I/O or throw an
  +     *      exception when they happen
  +     * @param useTimestamp whether to check the modified timestamp on the
  +     *      <code>destinationFile</code> against the remote <code>source</code>
  +     * @param proxyHost Proxy Host (if proxy is required), or null
  +     * @param proxyPort Proxy Port (if proxy is required), or null
  +     * @param proxyUserName Proxy Username (if authentification is required),
  +     *        or null
  +     * @param proxyPassword Proxy Password (if authentification is required),
  +     *        or null
  +     * @throws IOException If an I/O exception occurs.
  +     */
       public static void getFile( String url,
                                   File destinationFile,
                                   boolean ignoreErrors,
  @@ -88,7 +167,58 @@
                                   String proxyPort,
                                   String proxyUserName,
                                   String proxyPassword )
  -        throws Exception
  +        throws IOException
  +    {
  +        //set the timestamp to the file date.
  +        long timestamp = -1;
  +        if ( useTimestamp && destinationFile.exists() )
  +        {
  +            timestamp = destinationFile.lastModified();
  +        }
  +
  +        try
  +        {
  +            getFile( url,
  +                     destinationFile,
  +                     timestamp,
  +                     proxyHost,
  +                     proxyPort,
  +                     proxyUserName,
  +                     proxyPassword );
  +        }
  +        catch ( IOException ex )
  +        {
  +            if ( !ignoreErrors )
  +            {
  +                throw ex;
  +            }
  +        }
  +    }
  +
  +    /**
  +     * Retrieve a remote file.
  +     *
  +     * @param url the URL of the file to retrieve
  +     * @param destinationFile where to store it
  +     * @param timestamp if provided, the remote URL is only retrieved if it was
  +     * modified more recently than timestamp. Otherwise, negative value indicates that
  +     * the remote URL should be retrieved unconditionally.
  +     * @param proxyHost Proxy Host (if proxy is required), or null
  +     * @param proxyPort Proxy Port (if proxy is required), or null
  +     * @param proxyUserName Proxy Username (if authentification is required),
  +     *        or null
  +     * @param proxyPassword Proxy Password (if authentification is required),
  +     *        or null
  +     * @throws IOException If an I/O exception occurs.
  +     */
  +    public static void getFile( String url,
  +                                File destinationFile,
  +                                long timestamp,
  +                                String proxyHost,
  +                                String proxyPort,
  +                                String proxyUserName,
  +                                String proxyPassword )
  +      throws IOException
       {
           String[] s = parseUrl( url );
           String username = s[0];
  @@ -97,23 +227,14 @@
   
           URL source = new URL( parsedUrl );
   
  -        //set the timestamp to the file date.
  -        long timestamp = 0;
  -        boolean hasTimestamp = false;
  -        if ( useTimestamp && destinationFile.exists() )
  -        {
  -            timestamp = destinationFile.lastModified();
  -            hasTimestamp = true;
  -        }
  -
           //set proxy connection
           useProxyUser( proxyHost, proxyPort, proxyUserName, proxyPassword );
   
           //set up the URL connection
           URLConnection connection = source.openConnection();
  +
           //modify the headers
  -        //NB: things like user authentication could go in here too.
  -        if ( useTimestamp && hasTimestamp )
  +        if ( timestamp >= 0 )
           {
               connection.setIfModifiedSince( timestamp );
           }
  @@ -121,22 +242,7 @@
           if ( username != null || password != null )
           {
               String up = username + ":" + password;
  -            String encoding = null;
  -            // check to see if sun's Base64 encoder is available.
  -            try
  -            {
  -                sun.misc.BASE64Encoder encoder =
  -                    (sun.misc.BASE64Encoder) Class.forName(
  -                        "sun.misc.BASE64Encoder" ).newInstance();
  -
  -                encoding = encoder.encode( up.getBytes() );
  -            }
  -            catch ( Exception ex )
  -            {
  -                // Do nothing, as for MavenSession we will never use
  -                // auth and we will eventually move over httpclient
  -                // in the commons.
  -            }
  +            String encoding = Base64.encode(up.getBytes(), false);
               connection.setRequestProperty( "Authorization", "Basic " + encoding );
           }
   
  @@ -146,6 +252,14 @@
           if ( connection instanceof HttpURLConnection )
           {
               HttpURLConnection httpConnection = (HttpURLConnection) connection;
  +            // although HTTPUrlConnection javadocs says FileNotFoundException should be
  +            // thrown on a 404 error, that certainly does not appear to be the case, so
  +            // test for 404 ourselves, and throw FileNotFoundException as needed
  +            if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND)
  +            {
  +                throw new FileNotFoundException(url.toString() + " (HTTP Error: "
  +                        + httpConnection.getResponseCode() + " " + httpConnection.getResponseMessage() + ")");
  +            }
               if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED )
               {
                   return;
  @@ -153,7 +267,12 @@
               // test for 401 result (HTTP only)
               if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED )
               {
  -                throw new Exception( "Not authorized." );
  +                throw new IOException( "Not authorized." );
  +            }
  +            // test for 407 result (HTTP only)
  +            if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH )
  +            {
  +                throw new IOException( "Not authorized by proxy." );
               }
           }
   
  @@ -163,6 +282,7 @@
           // Some protocols (FTP) dont include dates, of course.
   
           InputStream is = null;
  +        IOException isException = null;
           for ( int i = 0; i < 3; i++ )
           {
               try
  @@ -172,20 +292,20 @@
               }
               catch ( IOException ex )
               {
  -                // do nothing
  +              isException = ex;
               }
           }
           if ( is == null )
           {
  -            if ( ignoreErrors )
  -            {
  -                return;
  -            }
  -
  -            // This will never happen with maven's use of this class.
  -            throw new Exception( "Can't get " + destinationFile.getName() + " to " + destinationFile );
  +            throw isException;
           }
   
  +        if ( connection.getLastModified() <= timestamp &&
  +             connection.getLastModified() != 0 )
  +        {
  +            return;
  +        }
  +        
           FileOutputStream fos = new FileOutputStream( destinationFile );
   
           byte[] buffer = new byte[100 * 1024];
  @@ -204,10 +324,9 @@
           // if (and only if) the use file time option is set, then the
           // saved file now has its timestamp set to that of the downloaded
           // file
  -        if ( useTimestamp )
  +        if ( timestamp >= 0 )
           {
               long remoteTimestamp = connection.getLastModified();
  -
               if ( remoteTimestamp != 0 )
               {
                   touchFile( destinationFile, remoteTimestamp );
  @@ -215,6 +334,15 @@
           }
       }
   
  +    /**
  +     * Parse an url which might contain a username and password. If the
  +     * given url doesn't contain a username and password then return the
  +     * origin url unchanged.
  +     *
  +     * @param url The url to parse.
  +     * @return The username, password and url.
  +     * @throws RuntimeException if the url is (very) invalid
  +     */
       static String[] parseUrl( String url )
       {
           String[] parsedUrl = new String[3];
  @@ -231,18 +359,28 @@
           int i = url.indexOf( "@" );
           if ( i > 0 )
           {
  -            String s = url.substring( 7, i );
  +            String protocol = url.substring( 0, url.indexOf("://") ) + "://";
  +            String s = url.substring( protocol.length(), i );
               int j = s.indexOf( ":" );
               parsedUrl[0] = s.substring( 0, j );
               parsedUrl[1] = s.substring( j + 1 );
  -            parsedUrl[2] = "http://" + url.substring( i + 1 );
  +            parsedUrl[2] = protocol + url.substring( i + 1 );
           }
   
           return parsedUrl;
       }
   
  +    /**
  +     * set the timestamp of a named file to a specified time.
  +     *
  +     * @param file the file to touch
  +     * @param timemillis in milliseconds since the start of the era
  +     * @return true if it succeeded. False means that this is a java1.1 system
  +     *      and that file times can not be set
  +     * @throws RuntimeException Thrown in unrecoverable error. Likely this
  +     *      comes from file access failures.
  +     */
       private static boolean touchFile( File file, long timemillis )
  -        throws Exception
       {
           long modifiedTime;
   
  @@ -256,7 +394,6 @@
           }
   
           file.setLastModified( modifiedTime );
  -
           return true;
       }
   }
  
  
  
  1.1                  maven-components/maven-mboot/src/main/Base64.java
  
  Index: Base64.java
  ===================================================================
  /*
   * ====================================================================
   * Copyright 2001-2004 The Apache Software Foundation.
   * 
   * 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.
   * ====================================================================
   */
  
  import java.io.ByteArrayOutputStream;
  
  // import org.apache.commons.logging.Log;
  // import org.apache.commons.logging.LogFactory;
  
  /**
   * Encode/Decode Base-64.
   * 
   * @author John Casey
   */
  public final class Base64
  {
  
      // private static final Log LOG = LogFactory.getLog( Base64.class );
  
      private static final String CRLF = System.getProperty( "line.separator" );
  
      private static final int LINE_END = 64;
  
      public static String encode( byte[] data )
      {
          return Base64.encode( data, true );
      }
  
      public static String encode( byte[] data, boolean useLineDelimiter )
      {
          if ( data == null )
          {
              return null;
          }
          else if ( data.length == 0 )
          {
              return "";
          }
  
          int padding = 3 - ( data.length % 3 );
  
          // if ( LOG.isDebugEnabled() )
          // {
              // LOG.debug( "padding = " + padding + "characters." );
          // }
  
          StringBuffer buffer = new StringBuffer();
  
          for ( int i = 0; i < data.length; i += 3 )
          {
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "iteration base offset = " + i );
              // }
  
              int neutral = ( data[i] < 0 ? data[i] + 256 : data[i] );
  
              int block = ( neutral & 0xff );
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "after first byte, block = " + Integer.toBinaryString( block ) );
              // }
  
              boolean inLastSegment = false;
  
              block <<= 8;
              if ( i + 1 < data.length )
              {
                  neutral = ( data[i + 1] < 0 ? data[i + 1] + 256 : data[i + 1] );
                  block |= ( neutral & 0xff );
              }
              else
              {
                  inLastSegment = true;
              }
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "after second byte, block = " + Integer.toBinaryString( block ) + "; inLastSegment = "
                                  // + inLastSegment );
              // }
  
              block <<= 8;
              if ( i + 2 < data.length )
              {
                  neutral = ( data[i + 2] < 0 ? data[i + 2] + 256 : data[i + 2] );
                  block |= ( neutral & 0xff );
              }
              else
              {
                  inLastSegment = true;
              }
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "after third byte, block = " + Integer.toBinaryString( block ) + "; inLastSegment = "
                                  // + inLastSegment );
              // }
  
              char[] encoded = new char[4];
              int encIdx = 0;
              encoded[0] = toBase64Char( ( block >>> 18 ) & 0x3f );
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "first character = " + encoded[0] );
              // }
  
              encoded[1] = toBase64Char( ( block >>> 12 ) & 0x3f );
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "second character = " + encoded[1] );
              // }
  
              if ( inLastSegment && padding > 1 )
              {
                  encoded[2] = '=';
              }
              else
              {
                  encoded[2] = toBase64Char( ( block >>> 6 ) & 0x3f );
              }
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "third character = " + encoded[2] );
              // }
  
              if ( inLastSegment && padding > 0 )
              {
                  encoded[3] = '=';
              }
              else
              {
                  encoded[3] = toBase64Char( block & 0x3f );
              }
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "fourth character = " + encoded[3] );
              // }
  
              buffer.append( encoded );
          }
  
          if ( useLineDelimiter )
          {
              return canonicalize( buffer.toString() );
          }
          else
          {
              return buffer.toString();
          }
      }
  
      public static byte[] decode( String src )
      {
          return Base64.decode( src, true );
      }
  
      public static byte[] decode( String src, boolean useLineDelimiter )
      {
          if ( src == null )
          {
              return null;
          }
          else if ( src.length() < 1 )
          {
              return new byte[0];
          }
  
          // if ( LOG.isDebugEnabled() )
          // {
              // LOG.debug( "pre-canonicalization = \n" + src );
          // }
          String data = src;
  
          if ( useLineDelimiter )
          {
              data = deCanonicalize( src );
          }
          // if ( LOG.isDebugEnabled() )
          // {
              // LOG.debug( "post-canonicalization = \n" + data );
          // }
  
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
  
          char[] input = data.toCharArray();
  
          int index = 0;
          for ( int i = 0; i < input.length; i += 4 )
          {
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "iteration base offset = " + i );
              // }
  
              int block = ( toBase64Int( input[i] ) & 0x3f );
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "block after first char [" + input[i] + "] = " + Integer.toBinaryString( block ) );
              // }
  
              block <<= 6;
              block |= ( toBase64Int( input[i + 1] ) & 0x3f );
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "block after second char [" + input[i + 1] + "] = " + Integer.toBinaryString( block ) );
              // }
  
              boolean inPadding = false;
              boolean twoCharPadding = false;
              block <<= 6;
              if ( input[i + 2] != '=' )
              {
                  block |= ( toBase64Int( input[i + 2] ) & 0x3f );
              }
              else
              {
                  twoCharPadding = true;
                  inPadding = true;
              }
  
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "block after third char [" + input[i + 2] + "] = " + Integer.toBinaryString( block ) );
              // }
  
              block <<= 6;
              if ( input[i + 3] != '=' )
              {
                  block |= ( toBase64Int( input[i + 3] ) & 0x3f );
              }
              else
              {
                  inPadding = true;
              }
  
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "block after fourth char [" + input[i + 3] + "] = " + Integer.toBinaryString( block ) );
              // }
  
              baos.write( ( block >>> 16 ) & 0xff );
              // if ( LOG.isDebugEnabled() )
              // {
                  // LOG.debug( "byte[" + ( index++ ) + "] = " + ( ( block >>> 16 ) & 0xff ) );
              // }
  
              if ( !inPadding || !twoCharPadding )
              {
                  baos.write( ( block >>> 8 ) & 0xff );
                  // if ( LOG.isDebugEnabled() )
                  // {
                      // LOG.debug( "byte[" + ( index++ ) + "] = " + ( ( block >>> 8 ) & 0xff ) );
                  // }
              }
  
              if ( !inPadding )
              {
                  baos.write( block & 0xff );
                  // if ( LOG.isDebugEnabled() )
                  // {
                      // LOG.debug( "byte[" + ( index++ ) + "] = " + ( block & 0xff ) );
                  // }
              }
          }
  
          byte[] result = baos.toByteArray();
          // if ( LOG.isDebugEnabled() )
          // {
              // LOG.debug( "byte array is " + result.length + " bytes long." );
          // }
  
          return result;
      }
  
      private static char toBase64Char( int input )
      {
          if ( input > -1 && input < 26 )
          {
              return ( char ) ( 'A' + input );
          }
          else if ( input > 25 && input < 52 )
          {
              return ( char ) ( 'a' + input - 26 );
          }
          else if ( input > 51 && input < 62 )
          {
              return ( char ) ( '0' + input - 52 );
          }
          else if ( input == 62 )
          {
              return '+';
          }
          else if ( input == 63 )
          {
              return '/';
          }
          else
          {
              return '?';
          }
      }
  
      private static int toBase64Int( char input )
      {
          if ( input >= 'A' && input <= 'Z' )
          {
              return input - 'A';
          }
          else if ( input >= 'a' && input <= 'z' )
          {
              return input + 26 - 'a';
          }
          else if ( input >= '0' && input <= '9' )
          {
              return input + 52 - '0';
          }
          else if ( input == '+' )
          {
              return 62;
          }
          else if ( input == '/' )
          {
              return 63;
          }
          else
          {
              return 0;
          }
      }
  
      private static String deCanonicalize( String data )
      {
          if ( data == null )
          {
              return null;
          }
  
          StringBuffer buffer = new StringBuffer( data.length() );
          for ( int i = 0; i < data.length(); i++ )
          {
              char c = data.charAt( i );
              if ( c != '\r' && c != '\n' )
              {
                  buffer.append( c );
              }
          }
  
          return buffer.toString();
      }
  
      private static String canonicalize( String data )
      {
          StringBuffer buffer = new StringBuffer( ( int ) ( data.length() * 1.1 ) );
  
          int col = 0;
          for ( int i = 0; i < data.length(); i++ )
          {
              if ( col == LINE_END )
              {
                  buffer.append( CRLF );
                  col = 0;
              }
  
              buffer.append( data.charAt( i ) );
              col++;
          }
  
          buffer.append( CRLF );
  
          return buffer.toString();
      }
  
  }
  
  
  

Re: cvs commit: maven-components/maven-mboot/src/main Base64.java HttpUtils.java

Posted by John Casey <jd...@commonjava.org>.
Ah, the human precompiler hard at work...

-j

On Fri, 2004-06-25 at 06:41, Jerome Lacoste wrote:
> On Fri, 2004-06-25 at 11:37, evenisse@apache.org wrote:
> > evenisse    2004/06/25 02:37:30
> > 
> >   Modified:    maven-mboot/src/main HttpUtils.java
> >   Added:       maven-mboot/src/main Base64.java
> >   Log:
> >   Upgrade HttpUtils from maven1
> 
>   
> [...]
> 
> >            String[] parsedUrl = new String[3];
> >   @@ -231,18 +359,28 @@
> >            int i = url.indexOf( "@" );
> >            if ( i > 0 )
> >            {
> >   -            String s = url.substring( 7, i );
> >   +            String protocol = url.substring( 0, url.indexOf("://") ) + "://";
> 
> String protocol = url.substring( 0, url.indexOf("://") + 3 );
> 
> 
-- 
John Casey
jdcasey@commonjava.org
CommonJava Open Components Project
http://www.commonjava.org


Re: cvs commit: maven-components/maven-mboot/src/main Base64.java HttpUtils.java

Posted by Jerome Lacoste <je...@coffeebreaks.org>.
On Fri, 2004-06-25 at 11:37, evenisse@apache.org wrote:
> evenisse    2004/06/25 02:37:30
> 
>   Modified:    maven-mboot/src/main HttpUtils.java
>   Added:       maven-mboot/src/main Base64.java
>   Log:
>   Upgrade HttpUtils from maven1

  
[...]

>            String[] parsedUrl = new String[3];
>   @@ -231,18 +359,28 @@
>            int i = url.indexOf( "@" );
>            if ( i > 0 )
>            {
>   -            String s = url.substring( 7, i );
>   +            String protocol = url.substring( 0, url.indexOf("://") ) + "://";

String protocol = url.substring( 0, url.indexOf("://") + 3 );