cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cschnei...@apache.org
Subject svn commit: r1049988 - in /cxf/trunk/rt/transports: common/ http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/ http/src/main/java/org/apache/cxf/transport/http/ http/src/main/java/org/apache/cxf/transport/servlet/ http/src/test/java/org/apache...
Date Thu, 16 Dec 2010 14:36:05 GMT
Author: cschneider
Date: Thu Dec 16 14:36:04 2010
New Revision: 1049988

URL: http://svn.apache.org/viewvc?rev=1049988&view=rev
Log:
CXF-3123 Some refactorings of the http auth code to make it easier to add new auth mechanisms

Added:
    cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
    cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
Modified:
    cxf/trunk/rt/transports/common/   (props changed)
    cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
    cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
    cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
    cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
    cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
    cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
    cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java

Propchange: cxf/trunk/rt/transports/common/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Thu Dec 16 14:36:04 2010
@@ -0,0 +1 @@
+target

Modified: cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
(original)
+++ cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
Thu Dec 16 14:36:04 2010
@@ -41,7 +41,7 @@ import org.apache.cxf.transports.http.Qu
 import org.apache.cxf.wsdl.http.AddressType;
 
 public class OsgiServletController extends AbstractServletController {
-    private static final Logger LOG = LogUtils.getL7dLogger(OsgiServlet.class);
+    private static final Logger LOG = LogUtils.getL7dLogger(OsgiServletController.class);
 
     public OsgiServletController(ServletConfig config, 
                                  DestinationRegistry destinationRegistry, 

Modified: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
(original)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
Thu Dec 16 14:36:04 2010
@@ -19,9 +19,6 @@
 
 package org.apache.cxf.transport.http;
 
-import java.io.IOException;
-import java.io.StreamTokenizer;
-import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.net.URL;
 import java.security.MessageDigest;
@@ -53,8 +50,7 @@ public class DigestAuthSupplier extends 
         }
         md5Helper = md;
     }
-    
-    
+
     /**
      * {@inheritDoc}
      * With digest, the nonce could expire and thus a rechallenge will be issued.
@@ -63,42 +59,14 @@ public class DigestAuthSupplier extends 
     public boolean requiresRequestCaching() {
         return true;
     }
-    
-    static Map<String, String> parseHeader(String fullHeader) {
-        
-        Map<String, String> map = new HashMap<String, String>();
-        fullHeader = fullHeader.substring(7);
-        try {
-            StreamTokenizer tok = new StreamTokenizer(new StringReader(fullHeader));
-            tok.quoteChar('"');
-            tok.quoteChar('\'');
-            tok.whitespaceChars('=', '=');
-            tok.whitespaceChars(',', ',');
-            
-            while (tok.nextToken() != StreamTokenizer.TT_EOF) {
-                String key = tok.sval;
-                if (tok.nextToken() == StreamTokenizer.TT_EOF) {
-                    map.put(key, null);
-                    return map;
-                }
-                String value = tok.sval;
-                if (value.charAt(0) == '"') {
-                    value = value.substring(1, value.length() - 1);
-                }
-                map.put(key, value);
-            }
-        } catch (IOException ex) {
-            //ignore
-        }
-        return map;
-    }
-    
+
     @Override
     public String getAuthorizationForRealm(HTTPConduit conduit, URL currentURL,
                                            Message message,
                                            String realm, String fullHeader) {
-        if (fullHeader.startsWith("Digest ")) {
-            Map<String, String> map = parseHeader(fullHeader);
+        HttpAuthHeader authHeader = new HttpAuthHeader(fullHeader);
+        if (authHeader.authTypeIsDigest()) {
+            Map<String, String> map = authHeader.getParams();
             if ("auth".equals(map.get("qop"))
                 || !map.containsKey("qop")) {
                 DigestInfo di = new DigestInfo();
@@ -160,6 +128,15 @@ public class DigestAuthSupplier extends 
         return null;
     }
 
+    public String createCnonce() throws UnsupportedEncodingException {
+        String cnonce = Long.toString(System.currentTimeMillis());
+        byte[] bytes = cnonce.getBytes("US-ASCII");
+        synchronized (md5Helper) {
+            bytes = md5Helper.digest(bytes);
+        }
+        return encode(bytes);
+    }
+
     class DigestInfo {
         String qop;
         String realm;
@@ -173,10 +150,7 @@ public class DigestAuthSupplier extends 
         synchronized String generateAuth(String uri, String username, String password) {
             try {
                 nc++;
-                String ncstring = Integer.toString(nc);
-                while (ncstring.length() < 8) {
-                    ncstring = "0" + ncstring;
-                }
+                String ncstring = String.format("%08d", nc);
                 String cnonce = createCnonce();
                 
                 String digAlg = algorithm;
@@ -188,14 +162,7 @@ public class DigestAuthSupplier extends 
                 if ("MD5-sess".equalsIgnoreCase(algorithm)) {
                     algorithm = "MD5";
                     String tmp2 = encode(digester.digest(a1.getBytes(charset)));
-                    StringBuilder tmp3 = new StringBuilder(
-                            tmp2.length() + nonce.length() + cnonce.length() + 2);
-                    tmp3.append(tmp2);
-                    tmp3.append(':');
-                    tmp3.append(nonce);
-                    tmp3.append(':');
-                    tmp3.append(cnonce);
-                    a1 = tmp3.toString();
+                    a1 = tmp2 + ':' + nonce + ':' + cnonce;
                 }
                 String hasha1 = encode(digester.digest(a1.getBytes(charset)));
                 String a2 = method + ":" + uri;
@@ -207,36 +174,20 @@ public class DigestAuthSupplier extends 
                     serverDigestValue = hasha1 + ":" + nonce + ":" + ncstring + ":" + cnonce
+ ":" 
                         + qop + ":" + hasha2;
                 }
-                serverDigestValue = encode(digester.digest(serverDigestValue.getBytes("US-ASCII")));
-                StringBuilder builder = new StringBuilder("Digest ");
+                String response = encode(digester.digest(serverDigestValue.getBytes("US-ASCII")));
+                Map<String, String> outParams = new HashMap<String, String>();
                 if (qop != null) {
-                    builder.append("qop=\"auth\", ");
-                }  
-                builder.append("realm=\"")
-                    .append(realm);
-
-                if (opaque != null) {
-                    builder.append("\", opaque=\"")
-                        .append(opaque);
+                    outParams.put("qop", "auth");
                 }
-
-                builder.append("\", nonce=\"")
-                    .append(nonce)
-                    .append("\", uri=\"")
-                    .append(uri)
-                    .append("\", username=\"")
-                    .append(username)
-                    .append("\", nc=")
-                    .append(ncstring)
-                    .append(", cnonce=\"")
-                    .append(cnonce)        
-                    .append("\", response=\"")
-                    .append(serverDigestValue)
-                    .append("\"");
-                
-                return builder.toString();
-            } catch (RuntimeException ex) {
-                throw ex;
+                outParams.put("realm", realm);
+                outParams.put("opaque", opaque);
+                outParams.put("nonce", nonce);
+                outParams.put("uri", uri);
+                outParams.put("username", username);
+                outParams.put("nc", ncstring);
+                outParams.put("cnonce", cnonce);
+                outParams.put("response", response);
+                return new HttpAuthHeader(HttpAuthHeader.AUTH_TYPE_DIGEST, outParams).getFullHeader();
             } catch (Exception ex) {
                 throw new RuntimeException(ex);
             }
@@ -245,15 +196,6 @@ public class DigestAuthSupplier extends 
         
     }
 
-    public String createCnonce() throws UnsupportedEncodingException {
-        String cnonce = Long.toString(System.currentTimeMillis());
-        byte[] bytes = cnonce.getBytes("US-ASCII");
-        synchronized (md5Helper) {
-            bytes = md5Helper.digest(bytes);
-        }
-        return encode(bytes);
-    }
-
     /**
      * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long 
      * <CODE>String</CODE> according to RFC 2617.

Modified: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
(original)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
Thu Dec 16 14:36:04 2010
@@ -44,7 +44,6 @@ import javax.xml.namespace.QName;
 import org.apache.cxf.Bus;
 import org.apache.cxf.common.injection.NoJSR250Annotations;
 import org.apache.cxf.common.logging.LogUtils;
-import org.apache.cxf.common.util.Base64Utility;
 import org.apache.cxf.configuration.Configurable;
 import org.apache.cxf.configuration.jsse.TLSClientParameters;
 import org.apache.cxf.configuration.security.AuthorizationPolicy;
@@ -755,7 +754,7 @@ public class HTTPConduit 
                 && authPolicy != null && authPolicy.isSetPassword()) {
                 passwd = authPolicy.getPassword();
             }
-            headers.setAuthorization(getBasicAuthHeader(userName, passwd));
+            headers.setAuthorization(HttpBasicAuthSupplier.getBasicAuthHeader(userName, passwd));
         } else if (authPolicy != null 
                 && authPolicy.isSetAuthorizationType() 
                 && authPolicy.isSetAuthorization()) {
@@ -769,7 +768,7 @@ public class HTTPConduit 
                 if (proxyAuthPolicy.isSetPassword()) {
                     passwd = proxyAuthPolicy.getPassword();
                 }
-                headers.setProxyAuthorization(getBasicAuthHeader(userName, passwd));
+                headers.setProxyAuthorization(HttpBasicAuthSupplier.getBasicAuthHeader(userName,
passwd));
             } else if (proxyAuthPolicy.isSetAuthorizationType() 
                        && proxyAuthPolicy.isSetAuthorization()) {
                 headers.setProxyAuthorization(proxyAuthPolicy.getAuthorizationType() + "
" 
@@ -943,12 +942,10 @@ public class HTTPConduit 
         switch(responseCode) {
         case HttpURLConnection.HTTP_MOVED_PERM:
         case HttpURLConnection.HTTP_MOVED_TEMP:
-            connection = 
-                redirectRetransmit(origConnection, message, cachedStream);
+            connection = redirectRetransmit(origConnection, message, cachedStream);
             break;
         case HttpURLConnection.HTTP_UNAUTHORIZED:
-            connection = 
-                authorizationRetransmit(origConnection, message, cachedStream);
+            connection = authorizationRetransmit(origConnection, message, cachedStream);
             break;
         default:
             break;
@@ -979,89 +976,24 @@ public class HTTPConduit 
         if (!getClient(message).isAutoRedirect()) {
             return connection;
         }
-
-        // We keep track of the redirections for redirect loop protection.
-        Set<String> visitedURLs = getSetVisitedURLs(message);
-        
-        String lastURL = connection.getURL().toString();
-        visitedURLs.add(lastURL);
-        
-        String newURL = extractLocation(connection.getHeaderFields());
+        URL newURL = extractLocation(connection.getHeaderFields());
+        detectRedirectLoop(getConduitName(), connection.getURL(), newURL, message);
         if (newURL != null) {
-            // See if we are being redirected in a loop as best we can,
-            // using string equality on URL.
-            if (visitedURLs.contains(newURL)) {
-                // We are in a redirect loop; -- bail
-                if (LOG.isLoggable(Level.INFO)) {
-                    LOG.log(Level.INFO, "Redirect loop detected on Conduit \"" 
-                        + getConduitName() 
-                        + "\" on '" 
-                        + newURL
-                        + "'");
-                }
-                throw new IOException("Redirect loop detected on Conduit \"" 
-                                      + getConduitName() 
-                                      + "\" on '" 
-                                      + newURL
-                                      + "'");
-            }
-            // We are going to redirect.
-            // Remove any Server Authentication Information for the previous
-            // URL.
             new Headers(message).removeAuthorizationHeaders();
             
-            URL url = new URL(newURL);
-            
             // If user configured this Conduit with preemptive authorization
             // it is meant to make it to the end. (Too bad that information
             // went to every URL along the way, but that's what the user 
             // wants!
             // TODO: Make this issue a security release note.
-            setHeadersByAuthorizationPolicy(message, url);
-            
-            connection = retransmit(
-                    connection, url, message, cachedStream);
+            setHeadersByAuthorizationPolicy(message, newURL);
+            connection.disconnect();
+            return retransmit(newURL, message, cachedStream);
         }
         return connection;
     }
 
     /**
-     * This function gets the Set of URLs on the message that is used to 
-     * keep track of the URLs that were used in getting authorization 
-     * information.
-     *
-     * @param message The message where the Set of URLs is stored.
-     * @return The modifiable set of URLs that were visited.
-     */
-    private static Set<String> getSetAuthoriationURLs(Message message) {
-        @SuppressWarnings("unchecked")
-        Set<String> authURLs = (Set<String>) message.get(KEY_AUTH_URLS);
-        if (authURLs == null) {
-            authURLs = new HashSet<String>();
-            message.put(KEY_AUTH_URLS, authURLs);
-        }
-        return authURLs;
-    }
-
-    /**
-     * This function get the set of URLs on the message that is used to keep
-     * track of the URLs that were visited in redirects.
-     * 
-     * If it is not set on the message, an new empty set is stored.
-     * @param message The message where the Set is stored.
-     * @return The modifiable set of URLs that were visited.
-     */
-    private static Set<String> getSetVisitedURLs(Message message) {
-        @SuppressWarnings("unchecked")
-        Set<String> visitedURLs = (Set<String>) message.get(KEY_VISITED_URLS);
-        if (visitedURLs == null) {
-            visitedURLs = new HashSet<String>();
-            message.put(KEY_VISITED_URLS, visitedURLs);
-        }
-        return visitedURLs;
-    }
-    
-    /**
      * This method performs a retransmit for authorization information.
      * 
      * @param connection The currently active connection.
@@ -1076,12 +1008,11 @@ public class HTTPConduit 
         Message message, 
         CacheAndWriteOutputStream cachedStream
     ) throws IOException {
-
+        HttpAuthHeader authHeader = new HttpAuthHeader(connection.getHeaderField("WWW-Authenticate"));
         // If we don't have a dynamic supply of user pass, then
         // we don't retransmit. We just die with a Http 401 response.
         if (authSupplier == null) {
-            String auth = connection.getHeaderField("WWW-Authenticate");
-            if (auth.startsWith("Digest ")) {
+            if (authHeader.authTypeIsDigest()) {
                 authSupplier = new DigestAuthSupplier();
             } else {
                 return connection;
@@ -1089,54 +1020,22 @@ public class HTTPConduit 
         }
         
         URL currentURL = connection.getURL();
-        
-        String realm = extractAuthorizationRealm(connection.getHeaderFields());
-
+        String realm = authHeader.getRealm();
         detectAuthorizationLoop(getConduitName(), message, currentURL, realm);
-        
-        String up = 
+        String authorizationToken = 
             authSupplier.getAuthorizationForRealm(
-                this, currentURL, message, realm, connection.getHeaderField("WWW-Authenticate"));
-        
-        // No user pass combination. We give up.
-        if (up == null) {
+                this, currentURL, message, realm, authHeader.getFullHeader());
+        if (authorizationToken == null) {
+            // authentication not possible => we give up
             return connection;
         }
 
-        new Headers(message).setAuthorization(up);
-        
-        // also adding cookie headers when retransmitting in case of a "401 Unauthorized"
response
+        new Headers(message).setAuthorization(authorizationToken);
         cookies.writeToMessageHeaders(message);
-        
-        return retransmit(
-                connection, currentURL, message, cachedStream);
+        connection.disconnect();
+        return retransmit(currentURL, message, cachedStream);
     }
 
-    private static void detectAuthorizationLoop(String conduitName, Message message, 
-            URL currentURL, String realm) throws IOException {
-        Set<String> authURLs = getSetAuthoriationURLs(message);
-        // If we have been here (URL & Realm) before for this particular message
-        // retransmit, it means we have already supplied information
-        // which must have been wrong, or we wouldn't be here again.
-        // Otherwise, the server may be 401 looping us around the realms.
-        if (authURLs.contains(currentURL.toString() + realm)) {
-            String logMessage = "Authorization loop detected on Conduit \""
-                + conduitName
-                + "\" on URL \""
-                + currentURL
-                + "\" with realm \""
-                + realm
-                + "\"";
-            if (LOG.isLoggable(Level.INFO)) {
-                LOG.log(Level.INFO, logMessage);
-            }
-                    
-            throw new IOException(logMessage);
-        }
-        // Register that we have been here before we go.
-        authURLs.add(currentURL.toString() + realm);
-    }
-    
     /**
      * This method retransmits the request.
      * 
@@ -1151,29 +1050,21 @@ public class HTTPConduit 
      * @throws IOException
      */
     private HttpURLConnection retransmit(
-            HttpURLConnection  connection,
             URL                newURL,
             Message            message, 
             CacheAndWriteOutputStream stream
     ) throws IOException {
-        
-        // Disconnect the old, and in with the new.
-        connection.disconnect();
-        
-        
         HTTPClientPolicy cp = getClient(message);
-        connection = createConnection(message, newURL);
+        HttpURLConnection  connection = createConnection(message, newURL);
         connection.setDoOutput(true);        
-        // TODO: using Message context to deceided HTTP send properties
+        // TODO: using Message context to decided HTTP send properties
         connection.setConnectTimeout((int)cp.getConnectionTimeout());
         connection.setReadTimeout((int)cp.getReceiveTimeout());
         connection.setUseCaches(false);
         connection.setInstanceFollowRedirects(false);
 
         // If the HTTP_REQUEST_METHOD is not set, the default is "POST".
-        String httpRequestMethod = 
-            (String)message.get(Message.HTTP_REQUEST_METHOD);
-
+        String httpRequestMethod = (String)message.get(Message.HTTP_REQUEST_METHOD);
         connection.setRequestMethod((null != httpRequestMethod) ? httpRequestMethod : "POST");
 
         message.put(KEY_HTTP_CONNECTION, connection);
@@ -1181,10 +1072,10 @@ public class HTTPConduit 
         if (stream != null) {
             connection.setFixedLengthStreamingMode(stream.size());
         }
-        
+
         // Need to set the headers before the trust decision
         // because they are set before the connect().
-        new Headers(message).setURLRequestHeaders(getConduitName());
+        new Headers(message).setProtocolHeadersInConnection(connection);
         
         //
         // This point is where the trust decision is made because the
@@ -1216,33 +1107,67 @@ public class HTTPConduit 
         return connection;
     }
 
+    private static void detectAuthorizationLoop(String conduitName, Message message, 
+                                                URL currentURL, String realm) throws IOException
{
+        @SuppressWarnings("unchecked")
+        Set<String> authURLs = (Set<String>) message.get(KEY_AUTH_URLS);
+        if (authURLs == null) {
+            authURLs = new HashSet<String>();
+            message.put(KEY_AUTH_URLS, authURLs);
+        }
+        // If we have been here (URL & Realm) before for this particular message
+        // retransmit, it means we have already supplied information
+        // which must have been wrong, or we wouldn't be here again.
+        // Otherwise, the server may be 401 looping us around the realms.
+        if (authURLs.contains(currentURL.toString() + realm)) {
+            String logMessage = "Authorization loop detected on Conduit \""
+                + conduitName
+                + "\" on URL \""
+                + currentURL
+                + "\" with realm \""
+                + realm
+                + "\"";
+            if (LOG.isLoggable(Level.INFO)) {
+                LOG.log(Level.INFO, logMessage);
+            }
+
+            throw new IOException(logMessage);
+        }
+        // Register that we have been here before we go.
+        authURLs.add(currentURL.toString() + realm);
+    }
+
     /**
-     * This function extracts the authorization realm from the 
-     * "WWW-Authenticate" Http response header.
+     * Tracks the visited urls in the message header KEY_VISITED_URLS.
+     * If a URL is to be visited twice an exception is thrown
      * 
-     * @param headers The Http Response Headers
-     * @return The realm, or null if it is non-existent.
+     * @param conduitName
+     * @param lastURL
+     * @param newURL
+     * @param message
+     * @throws IOException
      */
-    private String extractAuthorizationRealm(
-            Map<String, List<String>> headers
-    ) {
-        List<String> auth = headers.get("WWW-Authenticate");
-        if (auth != null) {
-            for (String a : auth) {
-                int idx = a.indexOf("realm=");
-                if (idx != -1) {
-                    a = a.substring(idx + 6);
-                    if (a.charAt(0) == '"') {
-                        a = a.substring(1, a.indexOf('"', 1));
-                    } else if (a.contains(",")) {
-                        a = a.substring(0, a.indexOf(','));
-                    }
-                    return a;
-                }
-            }
+    private static void detectRedirectLoop(String conduitName, 
+                                           URL lastURL, 
+                                           URL newURL,
+                                           Message message) throws IOException {
+        @SuppressWarnings("unchecked")
+        Set<String> visitedURLs = (Set<String>) message.get(KEY_VISITED_URLS);
+        if (visitedURLs == null) {
+            visitedURLs = new HashSet<String>();
+            message.put(KEY_VISITED_URLS, visitedURLs);
         }
-        return null;
-    }
+        visitedURLs.add(lastURL.toString());
+        if (newURL != null && visitedURLs.contains(newURL.toString())) {
+            // See if we are being redirected in a loop as best we can,
+            // using string equality on URL.
+            // We are in a redirect loop; -- bail
+            String msg = "Redirect loop detected on Conduit '" 
+                + conduitName + "' on '" + newURL + "'";
+            LOG.log(Level.INFO, msg);
+            throw new IOException(msg);
+        }
+    }    
     
     /**
      * This method extracts the value of the "Location" Http
@@ -1250,16 +1175,21 @@ public class HTTPConduit 
      * 
      * @param headers The Http response headers.
      * @return The value of the "Location" header, null if non-existent.
+     * @throws MalformedURLException 
      */
-    private String extractLocation(
-            Map<String, List<String>> headers
-    ) {
+    private URL extractLocation(Map<String, List<String>> headers
+                                ) throws MalformedURLException {
         
         for (Map.Entry<String, List<String>> head : headers.entrySet()) {
             if ("Location".equalsIgnoreCase(head.getKey())) {
                 List<String> locs = head.getValue();
                 if (locs != null && locs.size() > 0) {
-                    return locs.get(0);
+                    String location = locs.get(0);
+                    if (location != null) {
+                        return new URL(location);
+                    } else {
+                        return null;
+                    }
                 }                
             }
         }
@@ -1267,29 +1197,6 @@ public class HTTPConduit 
     }
 
     /**
-     * This procedure sets the "Authorization" header with the 
-     * BasicAuth token, which is Base64 encoded.
-     * 
-     * @param userid   The user's id, which cannot be null.
-     * @param password The password, it may be null.
-     * 
-     * @param headers  The headers map that gets the "Authorization" header set.
-     */
-    private String getBasicAuthHeader(
-        String                    userid,
-        String                    password
-    ) {
-        String userpass = userid;
-
-        userpass += ":";
-        if (password != null) {
-            userpass += password;
-        }
-        String token = Base64Utility.encode(userpass.getBytes());
-        return "Basic " + token;
-    }
-
-    /**
      * Wrapper output stream responsible for flushing headers and handling
      * the incoming HTTP-level response (not necessarily the MEP response).
      */
@@ -1370,13 +1277,23 @@ public class HTTPConduit 
                     throw e;
                 }
             }
+            
+            if (LOG.isLoggable(Level.FINE)) {
+                LOG.fine("Sending "
+                    + connection.getRequestMethod() 
+                    + " Message with Headers to " 
+                    + connection.getURL()
+                    + " Conduit :"
+                    + conduitName
+                    + "\nContent-Type: " + connection.getContentType() + "\n");
+            }
         }
         
         protected void handleHeadersTrustCaching() throws IOException {
             // Need to set the headers before the trust decision
             // because they are set before the connect().
-            new Headers(outMessage).setURLRequestHeaders(conduitName);
-           
+            new Headers(outMessage).setProtocolHeadersInConnection(connection);
+
             //
             // This point is where the trust decision is made because the
             // Sun implementation of URLConnection will not let us 
@@ -1499,37 +1416,24 @@ public class HTTPConduit 
                              + new String(cachedStream.getBytes()));
                 }
 
-                HttpURLConnection oldcon = connection;
-                
-                HTTPClientPolicy policy = getClient(outMessage);
-                
-                // Default MaxRetransmits is -1 which means unlimited.
-                int maxRetransmits = (policy == null)
-                                     ? -1
-                                     : policy.getMaxRetransmits();
-                
-                cookies.readFromConnection(oldcon);
-                
-                // MaxRetransmits of zero means zero.
-                if (maxRetransmits == 0) {
-                    return;
-                }
-                
+
+                int maxRetransmits = getMaxRetransmits();
+                cookies.readFromConnection(connection);
                 int nretransmits = 0;
-                
-                connection = processRetransmit(connection, outMessage, cachedStream);
-                
-                while (connection != oldcon) {
+                HttpURLConnection oldcon = null;
+                while (connection != oldcon && (maxRetransmits < 0 || nretransmits
< maxRetransmits)) {
                     nretransmits++;
                     oldcon = connection;
-
-                    // A negative max means unlimited.
-                    if (maxRetransmits < 0 || nretransmits < maxRetransmits) {
-                        connection = processRetransmit(connection, outMessage, cachedStream);
-                    }
+                    connection = processRetransmit(connection, outMessage, cachedStream);
                 }
             }
         }
+
+        private int getMaxRetransmits() {
+            HTTPClientPolicy policy = getClient(outMessage);
+            // Default MaxRetransmits is -1 which means unlimited.
+            return (policy == null) ? -1 : policy.getMaxRetransmits();
+        }
         
         /**
          * This procedure is called on the close of the output stream so

Modified: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
(original)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
Thu Dec 16 14:36:04 2010
@@ -246,18 +246,22 @@ public class Headers {
     }
     
     /**
-     * Put the headers from Message.PROTOCOL_HEADERS headers into the URL
-     * connection.
+     * Set content type and protocol headers (Message.PROTOCOL_HEADERS) headers into the
URL
+     * connection. 
      * Note, this does not mean they immediately get written to the output
      * stream or the wire. They just just get set on the HTTP request.
      * 
-     * @param message The outbound message.
+     * @param connection 
      * @throws IOException
      */
-    public void setURLRequestHeaders(String conduitName) throws IOException {
-        HttpURLConnection connection = 
-            (HttpURLConnection)message.get(KEY_HTTP_CONNECTION);
+    public void setProtocolHeadersInConnection(HttpURLConnection connection) throws IOException
{
+        String ct = determineContentType();
+        connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, ct);
+        transferProtocolHeadersToURLConnection(connection);
+        logProtocolHeaders(Level.FINE);
+    }
 
+    private String determineContentType() {
         String ct  = (String)message.get(Message.CONTENT_TYPE);
         String enc = (String)message.get(Message.ENCODING);
 
@@ -272,20 +276,7 @@ public class Headers {
         } else {
             ct = "text/xml";
         }
-        connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, ct);
-        
-        if (LOG.isLoggable(Level.FINE)) {
-            LOG.fine("Sending "
-                + connection.getRequestMethod() 
-                + " Message with Headers to " 
-                + connection.getURL()
-                + " Conduit :"
-                + conduitName
-                + "\nContent-Type: " + ct + "\n");
-            new Headers(message).logProtocolHeaders(Level.FINE);
-        }
-        
-        transferProtocolHeadersToURLConnection(connection);
+        return ct;
     }
     
     /**

Added: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java?rev=1049988&view=auto
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
(added)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
Thu Dec 16 14:36:04 2010
@@ -0,0 +1,139 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.cxf.transport.http;
+
+import java.io.IOException;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class HttpAuthHeader {
+    public static final String AUTH_TYPE_BASIC = "Basic";
+    public static final String AUTH_TYPE_DIGEST = "Digest";
+    public static final String AUTH_TYPE_NEGOTIATE = "Negotiate";
+
+    private String fullHeader;
+    private String authType;
+    private String fullContent;
+    private Map<String, String> params;
+
+    public HttpAuthHeader(String fullHeader) {
+        this.fullHeader = fullHeader;
+        int spacePos = fullHeader.indexOf(' ');
+        if (spacePos == -1) {
+            this.authType = fullHeader;
+        } else {
+            this.authType = fullHeader.substring(0, spacePos);
+            this.fullContent = fullHeader.substring(spacePos + 1);
+        }
+        this.params = parseHeader();
+    }
+    
+    public HttpAuthHeader(String authType, Map<String, String> params) {
+        this.authType = authType;
+        this.params = params;
+        this.fullContent = paramsToString();
+        this.fullHeader = authType + " " + fullContent;
+    }
+    
+    private String paramsToString() {
+        StringBuilder builder = new StringBuilder();
+        boolean first = true;
+        for (String key : params.keySet()) {
+            String param = params.get(key);
+            if (param != null) {
+                if (!first) {
+                    builder.append(", ");
+                }
+                builder.append(key + "=\"" + param + "\"");
+                first = false;
+            }
+        }
+        return builder.toString();
+    }
+
+    private Map<String, String> parseHeader() {
+        Map<String, String> map = new HashMap<String, String>();
+        try {
+            StreamTokenizer tok = new StreamTokenizer(new StringReader(this.fullContent));
+            tok.quoteChar('"');
+            tok.quoteChar('\'');
+            tok.whitespaceChars('=', '=');
+            tok.whitespaceChars(',', ',');
+            
+            while (tok.nextToken() != StreamTokenizer.TT_EOF) {
+                String key = tok.sval;
+                if (tok.nextToken() == StreamTokenizer.TT_EOF) {
+                    map.put(key, null);
+                    return map;
+                }
+                String value = tok.sval;
+                if (value.charAt(0) == '"') {
+                    value = value.substring(1, value.length() - 1);
+                }
+                map.put(key, value);
+            }
+        } catch (IOException ex) {
+            // ignore canĀ“t happen for StringReader
+        }
+        return map;
+    }
+
+    /**
+     * Extracts the authorization realm from the 
+     * "WWW-Authenticate" Http response header.
+     * 
+     * @param authenticate content of the WWW-Authenticate header
+     * @return The realm, or null if it is non-existent.
+     */
+    public String getRealm() {
+        Map<String, String> map = parseHeader();
+        return map.get("realm");
+    }
+
+    public boolean authTypeIsDigest() {
+        return AUTH_TYPE_DIGEST.equals(this.authType);
+    }
+    
+    public boolean authTypeIsBasic() {
+        return AUTH_TYPE_BASIC.equals(this.authType);
+    }
+    
+    public boolean authTypeIsNegotiate() {
+        return AUTH_TYPE_DIGEST.equals(this.authType);
+    }
+    
+    public String getAuthType() {
+        return authType;
+    }
+
+    public String getFullContent() {
+        return fullContent;
+    }
+
+    public String getFullHeader() {
+        return this.fullHeader;
+    }
+
+    public Map<String, String> getParams() {
+        return params;
+    }
+
+}

Modified: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
(original)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
Thu Dec 16 14:36:04 2010
@@ -62,6 +62,11 @@ public abstract class HttpBasicAuthSuppl
     protected HttpBasicAuthSupplier(String name) {
         super(name);
     }
+
+    public static String getBasicAuthHeader(String userName, String passwd) {
+        String userAndPass = userName + ":" + passwd;
+        return "Basic " + Base64Utility.encode(userAndPass.getBytes());
+    }
     
     @Override
     public String getAuthorizationForRealm(HTTPConduit conduit, URL currentURL, Message message,
@@ -72,19 +77,18 @@ public abstract class HttpBasicAuthSuppl
                                           message,
                                           realm);
         if (up != null) {
-            String key = up.getUserid() + ":" + up.getPassword();
-            return "Basic " + Base64Utility.encode(key.getBytes());
+            return HttpBasicAuthSupplier.getBasicAuthHeader(up.getUserid(), up.getPassword());
         }
         return null;
     }
+
     @Override
     public String getPreemptiveAuthorization(HTTPConduit conduit, URL currentURL, Message
message) {
         UserPass up = getPreemptiveUserPass(conduit.getConduitName(),
                                             currentURL,
                                             message);
         if (up != null) {
-            String key = up.getUserid() + ":" + up.getPassword();
-            return "Basic " + Base64Utility.encode(key.getBytes());
+            return HttpBasicAuthSupplier.getBasicAuthHeader(up.getUserid(), up.getPassword());
         }
         return null;
     }

Modified: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
(original)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
Thu Dec 16 14:36:04 2010
@@ -40,7 +40,7 @@ import org.apache.cxf.transports.http.Qu
 
 public abstract class AbstractServletController {
     protected static final String DEFAULT_LISTINGS_CLASSIFIER = "/services";
-    private static final Logger LOG = LogUtils.getL7dLogger(ServletController.class);
+    private static final Logger LOG = LogUtils.getL7dLogger(AbstractServletController.class);
     
     protected boolean isHideServiceList;
     protected boolean disableAddressUpdates;

Added: cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java?rev=1049988&view=auto
==============================================================================
--- cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
(added)
+++ cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
Thu Dec 16 14:36:04 2010
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.cxf.transport.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+
+import org.apache.cxf.configuration.security.AuthorizationPolicy;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.easymock.classextension.EasyMock;
+import org.easymock.classextension.IMocksControl;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class DigestAuthSupplierTest {
+
+    /**
+     * Tests that parseHeader correctly parses parameters that contain ==
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testCXF2370() throws Exception {
+        String origNonce = "MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==";
+        String fullHeader = "Digest realm=\"MyCompany realm.\", qop=\"auth\"," + "nonce=\""
+ origNonce
+                            + "\"";
+        Map<String, String> map = new HttpAuthHeader(fullHeader).getParams();
+        assertEquals(origNonce, map.get("nonce"));
+        assertEquals("auth", map.get("qop"));
+        assertEquals("MyCompany realm.", map.get("realm"));
+    }
+
+    @Test
+    public void testEncode() throws MalformedURLException {
+        String origNonce = "MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==";
+        String fullHeader = "Digest realm=\"MyCompany realm.\", qop=\"auth\"," + "nonce=\""
+ origNonce
+                            + "\"";
+        
+        /**
+         * Initialize DigestAuthSupplier that always uses the same cnonce so we always
+         * get the same response
+         */
+        DigestAuthSupplier authSupplier = new DigestAuthSupplier() {
+
+            @Override
+            public String createCnonce() throws UnsupportedEncodingException {
+                return "27db039b76362f3d55da10652baee38c";
+            }
+            
+        };
+        IMocksControl control = EasyMock.createControl();
+        HTTPConduit conduit = control.createMock(HTTPConduit.class);
+        AuthorizationPolicy authorizationPolicy = new AuthorizationPolicy();
+        authorizationPolicy.setUserName("testUser");
+        authorizationPolicy.setPassword("testPassword");
+        
+        EasyMock.expect(conduit.getAuthorization()).andReturn(authorizationPolicy).atLeastOnce();
+        URL url = new URL("http://myserver");
+        Message message = new MessageImpl();
+        control.replay();
+        String authToken = authSupplier
+            .getAuthorizationForRealm(conduit, url, message, "myRealm", fullHeader);
+        assertEquals("Digest response=\"28e616b6868f60aaf9b19bb5b172f076\", " 
+                     + "cnonce=\"27db039b76362f3d55da10652baee38c\", " 
+                     + "username=\"testUser\", nc=\"00000001\", " 
+                     + "nonce=\"MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==\",
"
+                     + "realm=\"MyCompany realm.\", qop=\"auth\", uri=\"\"", authToken);
+        control.verify();
+    }
+}

Modified: cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java
(original)
+++ cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java
Thu Dec 16 14:36:04 2010
@@ -37,6 +37,7 @@ import org.apache.cxf.message.MessageImp
 import org.apache.cxf.service.model.EndpointInfo;
 import org.apache.cxf.ws.addressing.EndpointReferenceType;
 import org.apache.cxf.wsdl.EndpointReferenceUtils;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -117,16 +118,7 @@ public class HTTPConduitTest extends Ass
                      "/bar/foo");
     }
     
-    @Test
-    public void testCXF2370() throws Exception {
-        String origNonce = "MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==";
-        String fullHeader = "Digest realm=\"MyCompany realm.\", qop=\"auth\","
-            + "nonce=\"" + origNonce + "\"";
-        Map<String, String> map = DigestAuthSupplier.parseHeader(fullHeader);
-        assertEquals(origNonce, map.get("nonce"));
-        assertEquals("auth", map.get("qop"));
-        assertEquals("MyCompany realm.", map.get("realm"));
-    }
+
 
     /**
      * Verfies one of the tenents of our interface -- the Conduit sets up



Mime
View raw message