incubator-heraldry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ket...@apache.org
Subject svn commit: r463013 [5/8] - in /incubator/heraldry/libraries/java: ./ trunk/ trunk/META-INF/ trunk/bin/ trunk/bin/com/ trunk/bin/com/janrain/ trunk/bin/com/janrain/openid/ trunk/bin/com/janrain/openid/consumer/ trunk/bin/com/janrain/openid/store/ trunk...
Date Wed, 11 Oct 2006 22:33:11 GMT
Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/MemoryStore.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/MemoryStore.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/MemoryStore.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/MemoryStore.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,222 @@
+/*
+ * MemoryStore.java Created on February 16, 2006, 10:38 AM
+ */
+
+package com.janrain.openid.store;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.janrain.openid.Association;
+import com.janrain.openid.Util;
+
+/**
+ * This class is an in-memory store implementation. It is serializable to
+ * support persistence across application restarts. It is thread-safe, to
+ * support concurrent access from multiple threads.
+ * 
+ * @author JanRain, Inc.
+ */
+public class MemoryStore extends OpenIDStore implements Serializable
+{
+    static final long serialVersionUID = 8099032624276762250L;
+    private static final String cls = "com.janrain.openid.store.MemoryStore";
+    private static final Logger logger = Logger.getLogger(cls);
+
+    private static final long NONCE_LIFE = 60 * 60 * 6; // six hours, in seconds
+
+    private Map assocMap = Collections.synchronizedMap(new HashMap());
+    private Map nonceMap = Collections.synchronizedMap(new HashMap());
+    private byte [] authKey;
+
+    /**
+     * Creates a new <code>MemoryStore</code> with a random authKey.
+     */
+    public MemoryStore()
+    {
+        authKey = Util.randomBytes(AUTH_KEY_LEN);
+    }
+
+    /**
+     * Creates a new <code>MemoryStore</code> with an authKey generated by
+     * calculating the sha-1 hash of the secret phrase passed in.
+     * 
+     * @param secretPhrase
+     *            a phrase to hash
+     */
+    public MemoryStore(String secretPhrase)
+    {
+        try
+        {
+            authKey = Util.sha1(secretPhrase.getBytes("UTF-8"));
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            logger.log(Level.SEVERE, "UTF-8 again?", e);
+        }
+    }
+
+    /**
+     * Creates a new <code>MemoryStore</code> with an authKey passed in by the
+     * user. The length of the key must be exactly
+     * <code>com.janrain.openid.store.OpenIDStore.AUTH_KEY_LEN</code> bytes.
+     * 
+     * @param authKey
+     *            the authKey to use
+     */
+    public MemoryStore(byte [] authKey)
+    {
+        if (authKey.length != AUTH_KEY_LEN)
+        {
+            throw new IllegalArgumentException("authKey not the correct length");
+        }
+        authKey = new byte[20];
+        System.arraycopy(authKey, 0, this.authKey, 0, AUTH_KEY_LEN);
+    }
+
+    public boolean useNonce(String nonce)
+    {
+        Long ts = (Long)nonceMap.remove(nonce);
+
+        return ts != null && Util.getTimeStamp() < ts.longValue() + NONCE_LIFE;
+    }
+
+    public void storeNonce(String nonce)
+    {
+        nonceMap.put(nonce, new Long(Util.getTimeStamp()));
+    }
+
+    public void storeAssociation(String serverUrl, Association assoc)
+    {
+        synchronized (assocMap)
+        {
+            List l = (List)assocMap.get(serverUrl);
+            if (l == null)
+            {
+                l = new ArrayList();
+                assocMap.put(serverUrl, l);
+            }
+            l.add(assoc);
+        }
+    }
+
+    public boolean removeAssociation(String serverUrl, String handle)
+    {
+        boolean found = false;
+
+        synchronized (assocMap)
+        {
+            List l = (List)assocMap.get(serverUrl);
+            if (l != null)
+            {
+                Iterator it = l.iterator();
+                while (it.hasNext())
+                {
+                    Association assoc = (Association)it.next();
+                    if (assoc.getHandle().equals(handle))
+                    {
+                        it.remove();
+                        found = true;
+                    }
+                }
+            }
+        }
+        return found;
+    }
+
+    public Association getAssociation(String serverUrl, String handle)
+    {
+        synchronized (assocMap)
+        {
+            List l = (List)assocMap.get(serverUrl);
+
+            Association result = null;
+
+            if (l == null)
+            {
+                // nothing to do, result is already null
+            }
+            else if (handle == null)
+            {
+                Iterator it = l.iterator();
+                while (it.hasNext())
+                {
+                    Association a = (Association)it.next();
+                    if (a.getRemainingLife() <= 0)
+                    {
+                        it.remove();
+                    }
+                    else if (result == null
+                            || a.getIssued() > result.getIssued())
+                    {
+                        result = a;
+                    }
+                }
+            }
+            else
+            {
+                Iterator it = l.iterator();
+                while (it.hasNext())
+                {
+                    Association a = (Association)it.next();
+                    if (a.getRemainingLife() <= 0)
+                    {
+                        it.remove();
+                    }
+                    else if (a.getHandle().equals(handle))
+                    {
+                        result = a;
+                    }
+                }
+            }
+
+            return result;
+        }
+    }
+
+    public byte [] getAuthKey()
+    {
+        return authKey;
+    }
+
+    public void gc()
+    {
+        long now = Util.getTimeStamp();
+
+        synchronized (nonceMap)
+        {
+            Iterator it = nonceMap.entrySet().iterator();
+            while (it.hasNext())
+            {
+                Map.Entry e = (Map.Entry)it.next();
+                Long l = (Long)e.getValue();
+                if (l.longValue() + NONCE_LIFE < now)
+                {
+                    it.remove();
+                }
+            }
+        }
+
+        synchronized (assocMap)
+        {
+            Iterator it = assocMap.entrySet().iterator();
+            while (it.hasNext())
+            {
+                Map.Entry e = (Map.Entry)it.next();
+                Association a = (Association)e.getValue();
+                if (a.getRemainingLife() <= 0)
+                {
+                    it.remove();
+                }
+            }
+        }
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/OpenIDStore.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/OpenIDStore.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/OpenIDStore.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/OpenIDStore.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,163 @@
+/*
+ * OpenIDStore.java Created on February 3, 2006, 12:39 PM
+ */
+
+package com.janrain.openid.store;
+
+import com.janrain.openid.Association;
+
+/**
+ * This is the superclass for all store objects the OpenID library uses. It is a
+ * single class that provides all of the persistence mechanisms that the OpenID
+ * library needs, for both servers and consumers.
+ * 
+ * @author JanRain, Inc.
+ */
+public abstract class OpenIDStore
+{
+    /**
+     * The length of the auth key that should be returned by the
+     * <code>getAuthKey</code> method.
+     */
+    public static final int AUTH_KEY_LEN = 20;
+
+    /**
+     * This method returns a key used to sign the tokens, to ensure that they
+     * haven't been tampered with in transit. It should return the same key
+     * every time it is called. The key returned should be
+     * <code>AUTH_KEY_LEN<code> bytes long.
+     * 
+     * @return
+     */
+    public abstract byte [] getAuthKey();
+
+    /**
+     * This method puts a <code>com.janrain.openid.Association</code> object
+     * into storage, retrievable by server URL and handle.
+     * 
+     * @param serverUrl
+     *            the URL of the identity server that this association is with.
+     *            Because of the way the server portion of the library uses this
+     *            interface, don't assume there are any limitations on the
+     *            character set of the input string. In particular, expect to
+     *            see unescaped non-url-safe characters in the server_url field.
+     * @param assoc
+     *            the <code>com.janrain.openid.Association</code> to store
+     */
+    public abstract void storeAssociation(String serverUrl, Association assoc);
+
+    /**
+     * <p>
+     * This method returns an C{L{Association <openid.association.Association>}}
+     * object from storage that matches the server URL and, if specified,
+     * handle. It returns C{None} if no such association is found or if the
+     * matching association is expired.
+     * </p>
+     * <p>
+     * If no handle is specified, the store may return any association which
+     * matches the server URL. If multiple associations are valid, the
+     * recommended return value for this method is the one that will remain
+     * valid for the longest duration.
+     * </p>
+     * <p>
+     * This method is allowed (and encouraged) to garbage collect expired
+     * associations when found. This method must not return expired
+     * associations.
+     * </p>
+     * 
+     * @param serverUrl
+     *            the URL of the identity server to get the association for.
+     *            Because of the way the server portion of the library uses this
+     *            interface, don't assume there are any limitations on the
+     *            character set of the input string. In particular, expect to
+     *            see unescaped non-url-safe characters in the server_url field.
+     * @param handle
+     *            the handle of the specific association to get, or null to
+     *            indicate that any association for the given server will work.
+     * @return the <code>com.janrain.openid.Association</code> for the given
+     *         serverUrl and handle
+     */
+    public abstract Association getAssociation(String serverUrl, String handle);
+
+    /**
+     * This method is equivalent to calling getAssociation(serverUrl, null);
+     * 
+     * @param serverUrl
+     *            the URL of the identity server to get the association for.
+     *            Because of the way the server portion of the library uses this
+     *            interface, don't assume there are any limitations on the
+     *            character set of the input string. In particular, expect to
+     *            see unescaped non-url-safe characters in the server_url field.
+     * @return the <code>com.janrain.openid.Association</code> for the given
+     *         serverUrl and handle
+     */
+    public Association getAssociation(String serverUrl)
+    {
+        return getAssociation(serverUrl, null);
+    }
+
+    /**
+     * This method removes the matching association if it's found, and returns
+     * whether the association was removed or not.
+     * 
+     * @param serverUrl
+     *            The URL of the identity server the association to remove
+     *            belongs to. Because of the way the server portion of the
+     *            library uses this interface, don't assume there are any
+     *            limitations on the character set of the input string. In
+     *            particular, expect to see unescaped non-url-safe characters in
+     *            the server_url field.
+     * @param handle
+     *            This is the handle of the association to remove. If there
+     *            isn't an association found that matches both the given URL and
+     *            handle, then there was no matching handle found.
+     * @return Returns whether or not the given association existed.
+     */
+    public abstract boolean removeAssociation(String serverUrl, String handle);
+
+    /**
+     * Stores a nonce. This is used by the consumer to prevent replay attacks.
+     * 
+     * @param nonce
+     *            The nonce to store.
+     */
+    public abstract void storeNonce(String nonce);
+
+    /**
+     * <p>
+     * This method is called when the library is attempting to use a nonce. If
+     * the nonce is in the store, this method removes it and returns a value
+     * which evaluates as true. Otherwise it returns a value which evaluates as
+     * false.
+     * </p>
+     * <p>
+     * This method is allowed and encouraged to treat nonces older than some
+     * period (a very conservative window would be 6 hours, for example) as no
+     * longer existing, and return False and remove them.
+     * </p>
+     * 
+     * @param nonce
+     * @return
+     */
+    public abstract boolean useNonce(String nonce);
+
+    /**
+     * <p>
+     * This method must return <code>true</code> if the store is a
+     * dumb-mode-style store. The default implementation returns
+     * <code>False</code>.
+     * </p>
+     * <p>
+     * In general, any custom subclass of <code>OpenIDStore</code> won't
+     * override this method, as custom subclasses are only likely to be created
+     * when the store is fully functional.
+     * </p>
+     * 
+     * @return <code>true</code> if the store works fully, <code>false</code>
+     *         if the consumer will have to use dumb mode to use this store.
+     */
+    public boolean isDumb()
+    {
+        return false;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/url/FetchResponse.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/url/FetchResponse.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/url/FetchResponse.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/url/FetchResponse.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,72 @@
+/*
+ * FetchResponse.java Created on December 15, 2005, 11:03 AM
+ */
+
+package com.janrain.url;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+
+/**
+ * @author JanRain, Inc.
+ */
+public class FetchResponse
+{
+    private int statusCode;
+    private String finalUrl;
+    private byte [] rawContent;
+    private String encoding;
+    private Map headers;
+
+    public FetchResponse(int statusCode, String finalUrl, byte [] rawContent,
+                         String encoding, Map headers)
+    {
+        this.statusCode = statusCode;
+        this.finalUrl = finalUrl;
+        this.rawContent = rawContent;
+        this.encoding = encoding;
+        this.headers = headers;
+    }
+
+    public int getStatusCode()
+    {
+        return statusCode;
+    }
+
+    public String getFinalUrl()
+    {
+        return finalUrl;
+    }
+
+    public byte [] getRawContent()
+    {
+        return rawContent;
+    }
+
+    public String getEncoding()
+    {
+        return encoding;
+    }
+
+    public String getContent()
+    {
+        try
+        {
+            return new String(rawContent, encoding);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            return null;
+        }
+    }
+
+    public Map getHeaders()
+    {
+        return headers;
+    }
+
+    public String toString()
+    {
+        return "FetchResponse with status " + statusCode;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/url/HTTPFetcher.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/url/HTTPFetcher.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/url/HTTPFetcher.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/url/HTTPFetcher.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,46 @@
+package com.janrain.url;
+
+import java.util.Map;
+
+/**
+ * @author JanRain, Inc.
+ */
+public abstract class HTTPFetcher
+{
+    private static HTTPFetcher f;
+
+    public static HTTPFetcher getFetcher()
+    {
+        if (f == null)
+        {
+            return f = new UrlConnectionFetcher();
+        }
+        else
+        {
+            return f;
+        }
+    }
+
+    public static void setFetcher(HTTPFetcher fetcher)
+    {
+        f = fetcher;
+    }
+
+    public FetchResponse fetch(String url)
+    {
+        return fetch(url, null, null);
+    }
+
+    public FetchResponse fetch(String url, String body)
+    {
+        return fetch(url, body, null);
+    }
+
+    public FetchResponse fetch(String url, Map headers)
+    {
+        return fetch(url, null, headers);
+    }
+
+    public abstract FetchResponse fetch(String url, String body, Map headers);
+
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/url/UrlConnectionFetcher.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/url/UrlConnectionFetcher.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/url/UrlConnectionFetcher.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/url/UrlConnectionFetcher.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,190 @@
+/*
+ * UrlConnectionFetcher.java Created on December 15, 2005, 1:01 PM
+ */
+
+package com.janrain.url;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author JanRain, Inc.
+ */
+public class UrlConnectionFetcher extends HTTPFetcher
+{
+    private static final String cls = "com.janrain.openid.consumer.UrlConnectionFetcher";
+
+    private static final Logger logger = Logger.getLogger(cls);
+
+    private FetchResponse interpretResponse(HttpURLConnection conn)
+            throws IOException
+    {
+        try
+        {
+            logger.entering(cls, "interpretResponse", conn);
+
+            String charset;
+
+            String contentType = conn.getContentType();
+            if (contentType == null)
+            {
+                logger.log(Level.WARNING, "No content-type specified.");
+                charset = "ISO-8859-1";
+            }
+            else
+            {
+                int csi = contentType.toLowerCase().indexOf("charset=");
+
+                if (csi > -1)
+                {
+                    try
+                    {
+                        charset = Charset.forName(
+                                contentType.substring(csi + 8)).name();
+                    }
+                    catch (UnsupportedCharsetException e)
+                    {
+                        logger.log(Level.WARNING,
+                                "Unable to use given charset", e);
+                        charset = "ISO-8859-1";
+                    }
+                }
+                else
+                {
+                    logger.log(Level.WARNING, "Unable to find a charset");
+                    charset = "ISO-8859-1";
+                }
+            }
+
+            int code = conn.getResponseCode();
+
+            ByteArrayOutputStream content;
+            InputStream is = null;
+            try
+            {
+                if (200 <= code && code < 300)
+                {
+                    is = conn.getInputStream();
+                }
+                else
+                {
+                    is = conn.getErrorStream();
+                }
+
+                content = new ByteArrayOutputStream();
+                byte [] b = new byte[1024];
+                int read;
+
+                while ((read = is.read(b)) >= 0)
+                    content.write(b, 0, read);
+            }
+            finally
+            {
+                if (is != null) is.close();
+            }
+
+            String finalUrl = conn.getURL().toString();
+            byte [] rawContent = content.toByteArray();
+            Map headers = new HashMap();
+
+            Iterator it = conn.getHeaderFields().entrySet().iterator();
+            while (it.hasNext())
+            {
+                Map.Entry e = (Map.Entry)it.next();
+                String key = (String)e.getKey();
+                if (key != null)
+                {
+                    key = key.toLowerCase();
+                }
+                List l = (List)e.getValue();
+                String value = (String)l.get(l.size() - 1);
+                headers.put(key, value);
+            }
+
+            FetchResponse result = new FetchResponse(code, finalUrl,
+                    rawContent, charset, headers);
+            logger.exiting(cls, "interpretResponse", result);
+            return result;
+        }
+        catch (IOException e)
+        {
+            logger.throwing(cls, "interpretResponse", e);
+            throw e;
+        }
+        catch (NumberFormatException nfe)
+        {
+            IOException ioe = new IOException("Bad server status response");
+            ioe.initCause(nfe);
+            logger.throwing(cls, "interpretResponse", ioe);
+            throw ioe;
+        }
+    }
+
+    public FetchResponse fetch(String url, String data, Map headers)
+    {
+        logger.entering(cls, "fetch", new Object[] {url, data, headers});
+
+        URL u;
+        try
+        {
+            u = new URL(url);
+        }
+        catch (MalformedURLException e)
+        {
+            logger.log(Level.WARNING, "Exception parsing input URL", e);
+            logger.exiting(cls, "fetch", null);
+            return null;
+        }
+
+        try
+        {
+            HttpURLConnection conn = (HttpURLConnection)u.openConnection();
+
+            boolean post = data != null;
+            if (post)
+            {
+                conn.setDoOutput(true);
+                conn.setRequestProperty("Content-Encoding", "UTF-8");
+            }
+
+            conn.connect();
+
+            if (post)
+            {
+                OutputStream os = conn.getOutputStream();
+                os.write(data.getBytes("UTF-8"));
+                os.close();
+            }
+
+            FetchResponse result = interpretResponse(conn);
+
+            logger.exiting(cls, "fetch", result);
+            return result;
+        }
+        catch (IOException e)
+        {
+            logger.log(Level.WARNING, "IO error fetching URL", e);
+            logger.exiting(cls, "fetch", null);
+            return null;
+        }
+        catch (ClassCastException e)
+        {
+            logger.log(Level.WARNING, "Not an HTTP or HTTPS URL", e);
+            logger.exiting(cls, "fetch", null);
+            return null;
+        }
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Constants.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Constants.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Constants.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Constants.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,10 @@
+package com.janrain.yadis;
+
+public interface Constants
+{
+    public static final String HEADER_NAME = "X-XRDS-Location";
+
+    public static final String CONTENT_TYPE = "application/xrds+xml";
+
+    public static final String ACCEPT_HEADER = "text/html; q=0.3, application/xhtml+xml; q=0.5, application/xrds+xml";
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryFailure.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryFailure.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryFailure.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryFailure.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,27 @@
+package com.janrain.yadis;
+
+public class DiscoveryFailure extends Exception
+{
+    private static final long serialVersionUID = 7818888610216271759L;
+
+    public DiscoveryFailure()
+    {
+        super();
+    }
+
+    public DiscoveryFailure(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+
+    public DiscoveryFailure(String message)
+    {
+        super(message);
+    }
+
+    public DiscoveryFailure(Throwable cause)
+    {
+        super(cause);
+    }
+
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryResult.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryResult.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryResult.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/DiscoveryResult.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,184 @@
+package com.janrain.yadis;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.janrain.url.FetchResponse;
+import com.janrain.url.HTTPFetcher;
+
+public class DiscoveryResult
+{
+    String requestUri;
+    String normalizedUri;
+    String xrdsUri;
+    String contentType;
+    String encoding;
+    byte [] content;
+
+    DiscoveryResult(String uri)
+    {
+        this.requestUri = uri;
+    }
+
+    public static DiscoveryResult fromURI(String uri) throws DiscoveryFailure
+    {
+        DiscoveryResult r = new DiscoveryResult(uri);
+
+        HTTPFetcher f = HTTPFetcher.getFetcher();
+
+        Map headers = new HashMap();
+        headers.put("Accept:", Constants.ACCEPT_HEADER);
+        FetchResponse resp = f.fetch(uri, headers);
+
+        if (resp == null)
+        {
+            throw new DiscoveryFailure("Unable to retreive page");
+        }
+
+        if (resp.getStatusCode() != 200)
+        {
+            throw new DiscoveryFailure("HTTP response status was "
+                    + resp.getStatusCode());
+        }
+
+        // Note the URL after following redirects
+        r.normalizedUri = resp.getFinalUrl();
+
+        // Attempt to find out if we already have the document
+        r.contentType = (String)resp.getHeaders().get("content-type");
+
+        String contentType = r.getContentType() == null ? "" : r
+                .getContentType();
+        contentType = contentType.split(";", 2)[0];
+
+        if (Constants.CONTENT_TYPE.equalsIgnoreCase(contentType))
+        {
+            r.xrdsUri = r.getNormalizedUri();
+        }
+        else
+        {
+            // Try the header
+            String yadisLocation = (String)resp.getHeaders().get(
+                    Constants.HEADER_NAME.toLowerCase());
+
+            if (yadisLocation == null)
+            {
+                yadisLocation = ParseHTML.findHTMLMeta(resp);
+            }
+
+            if (yadisLocation != null)
+            {
+                r.xrdsUri = yadisLocation;
+                resp = f.fetch(yadisLocation);
+                if (resp.getStatusCode() != 200)
+                {
+                    throw new DiscoveryFailure("HTTP response status was "
+                            + resp.getStatusCode());
+                }
+                r.contentType = (String)resp.getHeaders().get("content-type");
+                r.encoding = resp.getEncoding();
+            }
+        }
+
+        r.content = resp.getRawContent();
+        return r;
+    }
+
+    public boolean usedYadisLocation()
+    {
+        return normalizedUri.equals(xrdsUri);
+    }
+
+    public boolean isXRDS()
+    {
+        return usedYadisLocation()
+                || Constants.CONTENT_TYPE.equals(contentType);
+    }
+
+    public byte [] getContent()
+    {
+        return content == null ? new byte[0] : content;
+    }
+
+    public String getDecodedContent()
+    {
+        try
+        {
+            return new String(getContent(), encoding == null ? "ISO8859-1"
+                    : encoding);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            return "";
+        }
+    }
+
+    public String getContentType()
+    {
+        return contentType;
+    }
+
+    public String getNormalizedUri()
+    {
+        return normalizedUri;
+    }
+
+    public String getRequestUri()
+    {
+        return requestUri;
+    }
+
+    public String getXrdsUri()
+    {
+        return xrdsUri;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (!(obj instanceof DiscoveryResult))
+        {
+            return false;
+        }
+
+        DiscoveryResult o = (DiscoveryResult)obj;
+        if (requestUri == null ? o.requestUri != null : !requestUri
+                .equals(o.requestUri))
+        {
+            return false;
+        }
+
+        if (normalizedUri == null ? o.normalizedUri != null : !normalizedUri
+                .equals(o.normalizedUri))
+        {
+            return false;
+        }
+
+        if (xrdsUri == null ? o.xrdsUri != null : !xrdsUri.equals(o.xrdsUri))
+        {
+            return false;
+        }
+
+        if (encoding == null ? o.encoding == null : encoding.equals(o.encoding))
+        {
+            return Arrays.equals(getContent(), o.getContent());
+        }
+        else
+        {
+            try
+            {
+                return new String(getContent(), encoding).equals(new String(o
+                        .getContent(), o.encoding));
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                return Arrays.equals(getContent(), o.getContent());
+            }
+            catch (NullPointerException e)
+            {
+                return Arrays.equals(getContent(), o.getContent());
+            }
+        }
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ParseHTML.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ParseHTML.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ParseHTML.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ParseHTML.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,173 @@
+package com.janrain.yadis;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.html.HTML;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.HTML.Tag;
+
+import com.janrain.url.FetchResponse;
+
+public class ParseHTML
+{
+    private static String entityRE = "&#x([a-f0-9]+);";
+
+    private static Pattern entityPattern = Pattern.compile(entityRE,
+            Pattern.CASE_INSENSITIVE);
+
+    private static String hexEntities(String s)
+    {
+        Matcher m = entityPattern.matcher(s);
+        StringBuffer result = new StringBuffer();
+        while (m.find())
+        {
+            String replacement;
+            try
+            {
+                int i = Integer.parseInt(m.group(1), 16);
+                replacement = "&#" + String.valueOf(i) + ";";
+            }
+            catch (NumberFormatException e)
+            {
+                replacement = m.group();
+            }
+
+            m.appendReplacement(result, replacement);
+        }
+        m.appendTail(result);
+
+        return result.toString();
+    }
+
+    private static class Accessor extends HTMLEditorKit
+    {
+        private static final long serialVersionUID = 2721756213643570638L;
+
+        public Parser getParser()
+        {
+            return super.getParser();
+        }
+    }
+
+    private static class YadisHTMLParser extends HTMLEditorKit.ParserCallback
+    {
+        private static final int top = 0;
+        private static final int html = 1;
+        private static final int head = 2;
+        private static final int found = 3;
+        private static final int terminated = 4;
+
+        private int state;
+        private String result;
+
+        public String getResult()
+        {
+            return result;
+        }
+
+        public YadisHTMLParser()
+        {
+            state = top;
+        }
+
+        public void handleEndTag(Tag t, int pos)
+        {
+            if (t == HTML.Tag.HEAD || t == HTML.Tag.BODY || t == HTML.Tag.HTML)
+            {
+                state = terminated;
+            }
+        }
+
+        public void handleSimpleTag(Tag t, MutableAttributeSet a, int pos)
+        {
+            if (state == head && t == HTML.Tag.META)
+            {
+                String httpEquiv = (String)a
+                        .getAttribute(HTML.Attribute.HTTPEQUIV);
+
+                if (Constants.HEADER_NAME.equalsIgnoreCase(httpEquiv))
+                {
+                    String content = (String)a
+                            .getAttribute(HTML.Attribute.CONTENT);
+                    result = content;
+                    state = found;
+                }
+            }
+            else
+            {
+                handleEndTag(t, pos);
+            }
+        }
+
+        public void handleStartTag(Tag t, MutableAttributeSet a, int pos)
+        {
+            if (t == HTML.Tag.BODY)
+            {
+                state = terminated;
+            }
+
+            if (state == top)
+            {
+                if (t == HTML.Tag.HEAD)
+                {
+                    state = head;
+                }
+                else if (t == HTML.Tag.HTML)
+                {
+                    state = html;
+                }
+            }
+            else if (state == html)
+            {
+                if (t == HTML.Tag.HEAD)
+                {
+                    state = head;
+                }
+                else if (t == HTML.Tag.HTML)
+                {
+                    state = terminated;
+                }
+            }
+            else if (state == head)
+            {
+                if (t == HTML.Tag.HEAD || t == HTML.Tag.HTML)
+                {
+                    state = terminated;
+                }
+            }
+            else
+            {
+                state = terminated;
+            }
+        }
+
+    }
+
+    public static String findHTMLMeta(FetchResponse resp)
+    {
+        YadisHTMLParser cb = new YadisHTMLParser();
+
+        try
+        {
+            Reader r = new StringReader(hexEntities(resp.getContent()));
+            new Accessor().getParser().parse(r, cb, false);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            // nothing to do
+        }
+        catch (IOException e)
+        {
+            // nothing to do
+        }
+
+        return cb.getResult();
+    }
+
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Service.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Service.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Service.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/Service.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,12 @@
+package com.janrain.yadis;
+
+/**
+ * Comparisons should be done based on priority, but implementations are free to
+ * ignore that.
+ * 
+ * @author chowells
+ */
+public interface Service extends Comparable
+{
+    // This may actually be an empty interface.
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ServiceParser.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ServiceParser.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ServiceParser.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/ServiceParser.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,13 @@
+package com.janrain.yadis;
+
+import java.util.List;
+import java.util.Set;
+
+import org.w3c.dom.Node;
+
+public interface ServiceParser
+{
+    public abstract Set getSupportedTypes();
+
+    public abstract List parseService(Node n);
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDS.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDS.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDS.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDS.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,150 @@
+package com.janrain.yadis;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+public class XRDS
+{
+    public static final String XRDNS = "xri://$xrd*($v*2.0)";
+
+    private Document doc;
+
+    /**
+     * @param res
+     * @throws XRDSError
+     */
+    public XRDS(DiscoveryResult res) throws XRDSError
+    {
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        dbf.setCoalescing(true);
+        dbf.setIgnoringComments(true);
+        dbf.setNamespaceAware(true);
+        dbf.setValidating(false);
+
+        DocumentBuilder db;
+        try
+        {
+            db = dbf.newDocumentBuilder();
+        }
+        catch (ParserConfigurationException e)
+        {
+            throw new RuntimeException("oops");
+        }
+
+        try
+        {
+            doc = db.parse(new ByteArrayInputStream(res.getContent()));
+        }
+        catch (IOException e)
+        {
+            throw new XRDSError(e);
+        }
+        catch (SAXException e)
+        {
+            throw new XRDSError(e);
+        }
+    }
+
+    /**
+     * @param s
+     * @return
+     */
+    public List getServices(ServiceParser s)
+    {
+        List wrapped = new ArrayList();
+
+        NodeList services = doc.getElementsByTagNameNS(XRDNS, "Service");
+        for (int i = 0; i < services.getLength(); i++)
+        {
+            Node service = services.item(i);
+            NamedNodeMap attrs = service.getAttributes();
+            int p = Integer.MAX_VALUE;
+            Node priority = attrs.getNamedItem("priority");
+            if (priority != null)
+            {
+                try
+                {
+                    p = Integer.parseInt(priority.getNodeValue());
+                }
+                catch (NumberFormatException e)
+                {
+                    // nothing to do
+                }
+            }
+
+            NodeList children = service.getChildNodes();
+            for (int j = 0; j < children.getLength(); j++)
+            {
+                Node child = children.item(j);
+                if (child.getNodeType() == Node.ELEMENT_NODE
+                        && child.getNamespaceURI().equals(XRDNS)
+                        && child.getLocalName().equals("Type")
+                        && s.getSupportedTypes().contains(
+                                child.getLastChild().getNodeValue()))
+                {
+                    List parsed = s.parseService(service);
+                    Iterator it = parsed.iterator();
+                    while (it.hasNext())
+                    {
+                        wrapped.add(new ServiceWrapper(p, (Service)it.next()));
+                    }
+                    break;
+                }
+            }
+        }
+
+        Collections.sort(wrapped);
+        List result = new ArrayList();
+
+        Iterator it = wrapped.iterator();
+        while (it.hasNext())
+        {
+            ServiceWrapper w = (ServiceWrapper)it.next();
+            result.add(w.s);
+        }
+        return result;
+    }
+
+    private static class ServiceWrapper implements Comparable
+    {
+        private int priority;
+        Service s;
+
+        public ServiceWrapper(int priority, Service s)
+        {
+            this.priority = priority;
+            this.s = s;
+        }
+
+        public int compareTo(Object obj)
+        {
+            ServiceWrapper o = (ServiceWrapper)obj;
+            if (priority > o.priority)
+            {
+                return 1;
+            }
+            else if (priority < o.priority)
+            {
+                return -1;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDSError.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDSError.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDSError.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/yadis/XRDSError.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,27 @@
+package com.janrain.yadis;
+
+public class XRDSError extends Exception
+{
+    private static final long serialVersionUID = -8010668352742513893L;
+
+    public XRDSError()
+    {
+        super();
+    }
+
+    public XRDSError(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+
+    public XRDSError(String message)
+    {
+        super(message);
+    }
+
+    public XRDSError(Throwable cause)
+    {
+        super(cause);
+    }
+
+}

Added: incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/DiffieHellmanTest.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/DiffieHellmanTest.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/DiffieHellmanTest.java (added)
+++ incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/DiffieHellmanTest.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,132 @@
+/*
+ * DiffieHellmanTest.java JUnit based test Created on December 8, 2005, 4:36 PM
+ */
+
+package com.janrain.openid;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * @author JanRain, Inc.
+ */
+public class DiffieHellmanTest extends TestCase
+{
+
+    public DiffieHellmanTest(String testName)
+    {
+        super(testName);
+    }
+
+    protected void setUp() throws Exception
+    {
+    }
+
+    protected void tearDown() throws Exception
+    {
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite(DiffieHellmanTest.class);
+
+        return suite;
+    }
+
+    /**
+     * Test of getSharedSecret method, of class
+     * com.janrain.openid.DiffieHellman.
+     */
+    public void testGetSharedSecret()
+    {
+        System.out.println("testGetSharedSecret");
+
+        for (int i = 0; i < 10; i++)
+        {
+            DiffieHellman dh1 = new DiffieHellman();
+            DiffieHellman dh2 = new DiffieHellman();
+
+            BigInteger secret1 = dh1.getSharedSecret(dh2.getPublicKey());
+            BigInteger secret2 = dh2.getSharedSecret(dh1.getPublicKey());
+
+            Assert.assertEquals(secret1, secret2);
+        }
+    }
+
+    /**
+     * Test of getPublicKey method, of class com.janrain.openid.DiffieHellman.
+     */
+    public void testGetPublicKey()
+    {
+        System.out.println("testGetPublicKey");
+
+        /*
+         * No additional testing over that done for setPrivateKey is currently
+         * necessary.
+         */
+    }
+
+    /**
+     * Test of setPrivateKey method, of class com.janrain.openid.DiffieHellman.
+     */
+    public void testSetPrivateKey()
+    {
+        System.out.println("testSetPrivateKey");
+
+        InputStream is = null;
+        try
+        {
+            is = getClass().getResourceAsStream("dhpriv");
+            if (is == null)
+            {
+                fail("Unable to load test data");
+            }
+
+            InputStreamReader ir = new InputStreamReader(is, "US-ASCII");
+            BufferedReader br = new BufferedReader(ir);
+
+            DiffieHellman dh = new DiffieHellman();
+            while (true)
+            {
+                String testCase = br.readLine();
+                if (testCase == null)
+                {
+                    break;
+                }
+
+                String [] parts = testCase.split("\\s", 2);
+                BigInteger input = new BigInteger(parts[0]);
+                BigInteger expected = new BigInteger(parts[1]);
+                dh.setPrivateKey(input);
+                Assert.assertEquals(expected, dh.getPublicKey());
+            }
+        }
+        catch (IOException e)
+        {
+            fail("This shouldn't have happened");
+        }
+        finally
+        {
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (IOException e)
+                {
+                    // nothing to do
+                }
+            }
+        }
+    }
+
+}

Added: incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/UtilTest.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/UtilTest.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/UtilTest.java (added)
+++ incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/UtilTest.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,186 @@
+/*
+ * UtilTest.java JUnit based test Created on December 8, 2005, 5:12 PM
+ */
+
+package com.janrain.openid;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * @author JanRain, Inc.
+ */
+public class UtilTest extends TestCase
+{
+
+    public UtilTest(String testName)
+    {
+        super(testName);
+    }
+
+    protected void setUp() throws Exception
+    {
+    }
+
+    protected void tearDown() throws Exception
+    {
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite(UtilTest.class);
+
+        return suite;
+    }
+
+    /**
+     * Test of numberToBase64 method, of class com.janrain.openid.Util.
+     */
+    public void testNumberToBase64()
+    {
+        System.out.println("testNumberToBase64");
+
+        InputStream is = null;
+        try
+        {
+            is = getClass().getResourceAsStream("n2b64");
+            if (is == null)
+            {
+                fail("Unable to load test data");
+            }
+
+            InputStreamReader ir = new InputStreamReader(is, "US-ASCII");
+            BufferedReader br = new BufferedReader(ir);
+
+            while (true)
+            {
+                String testCase = br.readLine();
+                if (testCase == null)
+                {
+                    break;
+                }
+
+                String [] parts = testCase.split("\\s", 2);
+                BigInteger input = new BigInteger(parts[1]);
+                assertEquals(parts[0], Util.numberToBase64(input));
+            }
+        }
+        catch (IOException e)
+        {
+            fail("This shouldn't have happened");
+        }
+        finally
+        {
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (IOException e)
+                {
+                    // nothing to do
+                }
+            }
+        }
+    }
+
+    /**
+     * Test of base64ToNumber method, of class com.janrain.openid.Util.
+     */
+    public void testBase64ToNumber()
+    {
+        System.out.println("testBase64ToNumber");
+
+        InputStream is = null;
+        try
+        {
+            is = getClass().getResourceAsStream("n2b64");
+            if (is == null)
+            {
+                fail("Unable to load test data");
+            }
+
+            InputStreamReader ir = new InputStreamReader(is, "US-ASCII");
+            BufferedReader br = new BufferedReader(ir);
+
+            while (true)
+            {
+                String testCase = br.readLine();
+                if (testCase == null)
+                {
+                    break;
+                }
+
+                String [] parts = testCase.split("\\s", 2);
+                BigInteger expected = new BigInteger(parts[1]);
+                assertEquals(expected, Util.base64ToNumber(parts[0]));
+            }
+        }
+        catch (IOException e)
+        {
+            fail("This shouldn't have happened");
+        }
+        finally
+        {
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (IOException e)
+                {
+                    // nothing to do
+                }
+            }
+        }
+    }
+
+    public void testNormalizeUrl()
+    {
+        System.out.println("testNormalizeUrl");
+
+        assertEquals("http://foo.com/", Util.normalizeUrl("foo.com"));
+
+        assertEquals("http://foo.com/", Util.normalizeUrl("http://foo.com"));
+        assertEquals("https://foo.com/", Util.normalizeUrl("https://foo.com"));
+        assertEquals("http://foo.com/bar", Util.normalizeUrl("foo.com/bar"));
+        assertEquals("http://foo.com/bar", Util
+                .normalizeUrl("http://foo.com/bar"));
+
+        assertEquals("http://foo.com/", Util.normalizeUrl("http://foo.com/"));
+        assertEquals("https://foo.com/", Util.normalizeUrl("https://foo.com/"));
+        assertEquals("https://foo.com/bar", Util
+                .normalizeUrl("https://foo.com/bar"));
+
+        assertEquals("http://foo.com/%E8%8D%89", Util
+                .normalizeUrl("foo.com/\u8349"));
+        assertEquals("http://foo.com/%E8%8D%89", Util
+                .normalizeUrl("http://foo.com/\u8349"));
+
+        assertEquals("http://xn--vl1a.com/", Util.normalizeUrl("\u8349.com"));
+        assertEquals("http://xn--vl1a.com/", Util
+                .normalizeUrl("http://\u8349.com"));
+        assertEquals("http://xn--vl1a.com/", Util.normalizeUrl("\u8349.com/"));
+        assertEquals("http://xn--vl1a.com/", Util
+                .normalizeUrl("http://\u8349.com/"));
+
+        assertEquals("http://xn--vl1a.com/%E8%8D%89", Util
+                .normalizeUrl("\u8349.com/\u8349"));
+        assertEquals("http://xn--vl1a.com/%E8%8D%89", Util
+                .normalizeUrl("http://\u8349.com/\u8349"));
+
+        assertNull(Util.normalizeUrl(null));
+        assertNull(Util.normalizeUrl(""));
+        assertNull(Util.normalizeUrl("http://"));
+    }
+
+}

Added: incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/ConsumerTest.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/ConsumerTest.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/ConsumerTest.java (added)
+++ incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/ConsumerTest.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,594 @@
+/*
+ * OpenIDConsumerTest.java JUnit based test Created on February 27, 2006, 3:35
+ * PM
+ */
+
+package com.janrain.openid.consumer;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.janrain.openid.Association;
+import com.janrain.openid.DiffieHellman;
+import com.janrain.openid.Util;
+import com.janrain.openid.store.MemoryStore;
+import com.janrain.openid.store.OpenIDStore;
+import com.janrain.url.FetchResponse;
+import com.janrain.url.HTTPFetcher;
+
+/**
+ * @author JanRain, Inc.
+ */
+public class ConsumerTest extends TestCase
+{
+    private static Logger debug = Logger
+            .getLogger("com.janrain.openid.consumer");
+
+    String serverUrl = "http://server.example.com/";
+    String consumerUrl = "http://consumer.example.com/";
+    byte [] secret0;
+    byte [] secret1;
+    String handle0;
+    String handle1;
+
+    public ConsumerTest(String testName)
+    {
+        super(testName);
+    }
+
+    protected void setUp() throws Exception
+    {
+        secret0 = "another 20-byte key.".getBytes("UTF-8");
+        secret1 = new byte[20];
+
+        handle0 = "Snarky";
+        handle1 = "Zeroes";
+
+        debug.setLevel(Level.OFF);
+
+        Handler [] handlers = debug.getHandlers();
+        for (int i = 0; i < handlers.length; i++)
+        {
+            debug.removeHandler(handlers[i]);
+        }
+
+        Handler h = new ConsoleHandler();
+        h.setLevel(Level.ALL);
+        debug.addHandler(h);
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite();
+
+        suite.addTestSuite(ConsumerTest.class);
+        suite.addTestSuite(SetupNeededTest.class);
+        suite.addTestSuite(CheckAuthTriggeredTest.class);
+
+        return suite;
+    }
+
+    private Map parse(String query)
+    {
+        Map result = new HashMap();
+        String [] args = query.split("&");
+
+        try
+        {
+            for (int i = 0; i < args.length; i++)
+            {
+                String [] kv = args[i].split("=");
+                String k = URLDecoder.decode(kv[0], "UTF-8");
+                String v = URLDecoder.decode(kv[1], "UTF-8");
+                if (result.put(k, v) != null)
+                {
+                    fail("Key " + k + " used twice");
+                }
+            }
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            // will never happen
+            throw new RuntimeException(e);
+        }
+
+        return result;
+    }
+
+    private static FetchResponse build(int code, String url, String data)
+    {
+        Map h = new HashMap();
+        String encoding = "UTF-8";
+        try
+        {
+            return new FetchResponse(code, url, data.getBytes(encoding),
+                    encoding, h);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            // this won't happen
+            return null;
+        }
+    }
+
+    private class TestFetcher extends HTTPFetcher
+    {
+        private String userUrl;
+        private String userPage;
+        private byte [] secret;
+        private String handle;
+        public int numAssocs = 0;
+
+        public TestFetcher(String userUrl, String userPage, byte [] secret,
+                           String handle)
+        {
+            this.userUrl = userUrl;
+            this.userPage = userPage;
+            this.secret = secret;
+            this.handle = handle;
+        }
+
+        public FetchResponse fetch(String url, String data, Map headers)
+        {
+            if (data == null)
+            {
+                if (url.equals(userUrl))
+                {
+                    return build(200, url, userPage);
+                }
+                else
+                {
+                    return build(404, url, "Not Found");
+                }
+            }
+            else
+            {
+                if (data.indexOf("openid.mode=associate") < 0)
+                {
+                    return build(400, url, "error:bad request\r\n");
+                }
+
+                Map query = parse(data);
+
+                assertTrue(query.size() == 4 || query.size() == 6);
+                assertEquals("associate", query.get("openid.mode"));
+                assertEquals("HMAC-SHA1", query.get("openid.assoc_type"));
+                assertEquals("DH-SHA1", query.get("openid.session_type"));
+
+                DiffieHellman d = new DiffieHellman((String)query
+                        .get("openid.dh_modulus"), (String)query
+                        .get("openid.dh_gen"));
+
+                BigInteger cpub = Util.base64ToNumber((String)query
+                        .get("openid.dh_consumer_public"));
+
+                String encMacKey = Util.toBase64(d.xorSecret(cpub, secret));
+
+                Map reply = new HashMap();
+                reply.put("assoc_type", "HMAC-SHA1");
+                reply.put("assoc_handle", handle);
+                reply.put("expires_in", "600");
+                reply.put("session_type", "DH-SHA1");
+                reply.put("dh_server_public", Util.numberToBase64(d
+                        .getPublicKey()));
+                reply.put("enc_mac_key", encMacKey);
+
+                numAssocs++;
+
+                return build(200, url, Util.toKVForm(null, reply));
+            }
+        }
+    }
+
+    private String createUserPage(String links)
+    {
+        return "<html>\r\n" + "  <head>\r\n"
+                + "    <title>A user page</title>\r\n" + "    " + links
+                + "\r\n" + "  </head>\r\n" + "  <body>\r\n"
+                + "    blah blah\r\n" + "  </body>\r\n" + "</html>\r\n";
+    }
+
+    public void checkSuccess(final String userUrl, final String delegateUrl,
+            String links, final boolean immediate)
+    {
+        final Map session = new HashMap();
+        final MemoryStore store = new MemoryStore();
+        final String mode = immediate ? "checkid_immediate" : "checkid_setup";
+
+        final String userPage = createUserPage(links);
+
+        HTTPFetcher old = HTTPFetcher.getFetcher();
+        TestFetcher fetcher = new TestFetcher(userUrl, userPage, secret0,
+                handle0);
+        HTTPFetcher.setFetcher(fetcher);
+
+        final Consumer consumer = new Consumer(session, store);
+
+        Runnable t = new Runnable()
+        {
+            public void run()
+            {
+                AuthRequest a;
+                try
+                {
+                    a = consumer.begin(userUrl);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+
+                String redirect = a.redirectUrl(consumerUrl, consumerUrl,
+                        immediate);
+                try
+                {
+                    Map expected = new HashMap();
+                    expected.put("openid.mode", mode);
+                    expected.put("openid.identity", delegateUrl);
+                    expected.put("openid.trust_root", consumerUrl);
+                    expected.put("openid.assoc_handle", handle0);
+
+                    Map actual = parse(new URL(redirect).getQuery());
+                    String rt = (String)actual.remove("openid.return_to");
+                    actual.remove(Consumer.getNonceName());
+
+                    assertEquals(new TreeMap(expected), new TreeMap(actual));
+                    assertTrue(rt.startsWith(consumerUrl));
+                }
+                catch (MalformedURLException e)
+                {
+                    throw new RuntimeException(e);
+                }
+
+                assertTrue(redirect.startsWith(serverUrl));
+
+                Map query = new HashMap();
+                query.put("openid.mode", "id_res");
+                query.put("openid.return_to", Util.appendArgs(consumerUrl, a
+                        .getReturnToArgs()));
+                query.put("openid.identity", delegateUrl);
+                query.put("openid.assoc_handle", handle0);
+                query.putAll(a.getReturnToArgs());
+
+                Association assoc = store.getAssociation(serverUrl, handle0);
+                assertNotNull(assoc);
+                assoc.signMap(Arrays.asList(new String[] {"mode", "return_to",
+                        "identity"}), query);
+
+                Response b = consumer.complete(query);
+                assertEquals(StatusCode.SUCCESS, b.getStatus());
+                assertEquals(userUrl, ((SuccessResponse)b).getIdentityUrl());
+            }
+        };
+
+        assertEquals(0, fetcher.numAssocs);
+        t.run();
+        assertEquals(1, fetcher.numAssocs);
+
+        // Test that doing it again uses the existing association
+        t.run();
+        assertEquals(1, fetcher.numAssocs);
+
+        // Another association is created if we remove the existing one
+        store.removeAssociation(serverUrl, handle0);
+        t.run();
+        assertEquals(2, fetcher.numAssocs);
+
+        // Test that doing it again uses the existing association
+        t.run();
+        assertEquals(2, fetcher.numAssocs);
+
+        // restore old fetcher
+        HTTPFetcher.setFetcher(old);
+    }
+
+    public void testSuccess()
+    {
+        String userUrl = "http://www.example.com/user.html";
+        String links = "<link rel=\"openid.server\" href=\"" + serverUrl
+                + "\" />";
+
+        String delegateUrl = "http://consumer.example.com/user";
+        String delegateLinks = "<link rel=\"openid.server\" href=\""
+                + serverUrl + "\" /> <link rel=\"openid.delegate\" href=\""
+                + delegateUrl + "\" />";
+
+        checkSuccess(userUrl, userUrl, links, false);
+        checkSuccess(userUrl, userUrl, links, true);
+
+        checkSuccess(userUrl, delegateUrl, delegateLinks, false);
+        checkSuccess(userUrl, delegateUrl, delegateLinks, true);
+    }
+
+    private class FailingFetcher extends HTTPFetcher
+    {
+        public FetchResponse fetch(String url, String data, Map headers)
+        {
+            try
+            {
+                String [] pieces = url.split("/");
+                int status = Integer.parseInt(pieces[pieces.length - 1]);
+                return build(status, url, "");
+            }
+            catch (Exception e)
+            {
+                return null;
+            }
+        }
+    }
+
+    public void testBadFetch()
+    {
+        OpenIDStore store = new MemoryStore();
+        HTTPFetcher old = HTTPFetcher.getFetcher();
+
+        HTTPFetcher fetcher = new FailingFetcher();
+        HTTPFetcher.setFetcher(fetcher);
+        Map session = new HashMap();
+        Consumer consumer = new Consumer(session, store);
+
+        String [] cases = {"http://network.error/error",
+                "http://not.found/404", "http://bad.request/400",
+                "http://server.error/500"};
+
+        for (int i = 0; i < cases.length; i++)
+        {
+            try
+            {
+                consumer.begin(cases[i]);
+                fail("Didn't get an IOException on a case that couldn't fetch");
+            }
+            catch (IOException e)
+            {
+                // expected
+            }
+        }
+
+        HTTPFetcher.setFetcher(old);
+    }
+
+    public void testBadParse()
+    {
+        OpenIDStore store = new MemoryStore();
+        String userUrl = "http://user.example.com/";
+        String [] cases = {"", "http://not.in.a.link.tag/",
+                "<link rel=\"openid.server\" href=\"not.in.html.or.head\" />"};
+
+        for (int i = 0; i < cases.length; i++)
+        {
+            HTTPFetcher old = HTTPFetcher.getFetcher();
+            TestFetcher f = new TestFetcher(userUrl, cases[i], null, null);
+            HTTPFetcher.setFetcher(f);
+            Map session = new HashMap();
+
+            Consumer consumer = new Consumer(session, store);
+
+            try
+            {
+                consumer.begin(userUrl);
+                fail("Didn't get an IOException on a case that couldn't parse");
+            }
+            catch (IOException e)
+            {
+                // expected
+            }
+
+            HTTPFetcher.setFetcher(old);
+        }
+    }
+
+    private static class CheckAuthHappened extends RuntimeException
+    {
+        private static final long serialVersionUID = -2195970999996174596L;
+    }
+
+    private static class ExceptingFetcher extends HTTPFetcher
+    {
+        public FetchResponse fetch(String url, String data, Map headers)
+        {
+            if (data != null)
+            {
+                throw new CheckAuthHappened();
+            }
+            else
+            {
+                return null;
+            }
+        }
+    }
+
+    public static class IdResBase extends TestCase
+    {
+        private HTTPFetcher old;
+        public HTTPFetcher fetcher = new ExceptingFetcher();
+        public String nonce = "nonny";
+        public String returnTo = "http://return.to/?" + Consumer.getNonceName()
+                + "=" + nonce;
+        public String serverId = "sirod";
+        public String serverUrl = "serlie";
+        public String consumerId = "consu";
+        public OpenIDStore store;
+        public Consumer consumer;
+
+        public IdResBase(String testName)
+        {
+            super(testName);
+        }
+
+        protected void setUp() throws Exception
+        {
+            old = HTTPFetcher.getFetcher();
+            HTTPFetcher.setFetcher(fetcher);
+            Map session = new HashMap();
+            store = new MemoryStore();
+            consumer = new Consumer(session, store);
+
+            Token t = new Token(store.getAuthKey(), consumerId, serverId,
+                    serverUrl);
+            session.put("_openid_consumer_last_token", t);
+        }
+
+        protected void tearDown() throws Exception
+        {
+            HTTPFetcher.setFetcher(old);
+        }
+    }
+
+    public static class SetupNeededTest extends IdResBase
+    {
+        public SetupNeededTest(String testName)
+        {
+            super(testName);
+        }
+
+        public void testSetupNeeded()
+        {
+            String setupUrl = "http://unittest/setup-here";
+            Map query = new HashMap();
+            query.put("openid.mode", "id_res");
+            query.put("openid.user_setup_url", setupUrl);
+
+            Response r = consumer.complete(query);
+            assertEquals(StatusCode.SETUP_NEEDED, r.getStatus());
+            assertEquals(setupUrl, ((SetupNeededResponse)r).getSetupUrl());
+        }
+    }
+
+    public static class CheckAuthTriggeredTest extends IdResBase
+    {
+        public CheckAuthTriggeredTest(String testName)
+        {
+            super(testName);
+        }
+
+        public void testCheckAuthTriggered()
+        {
+            Map query = new HashMap();
+            query.put("openid.mode", "id_res");
+            query.put("openid.return_to", returnTo);
+            query.put("openid.identity", serverId);
+            query.put("openid.assoc_handle", "notFound");
+            query.put("openid.sig", "whatever");
+            query.put("openid.signed", "mode");
+            query.put(Consumer.getNonceName(), nonce);
+
+            try
+            {
+                Response r = consumer.complete(query);
+                fail(r.getStatus().toString());
+            }
+            catch (CheckAuthHappened e)
+            {
+                // this is the expected case
+            }
+        }
+
+        public void testCheckAuthTriggeredWithAssoc()
+        {
+            Association assoc = new Association("handle", new byte[20], 1000,
+                    "HMAC-SHA1");
+
+            store.storeAssociation(serverUrl, assoc);
+
+            Map query = new HashMap();
+            query.put("openid.mode", "id_res");
+            query.put("openid.return_to", returnTo);
+            query.put("openid.identity", serverId);
+            query.put("openid.assoc_handle", "notFound");
+            query.put("openid.sig", "whatever");
+            query.put("openid.signed", "mode");
+            query.put(Consumer.getNonceName(), nonce);
+
+            try
+            {
+                Response r = consumer.complete(query);
+                fail(r.getStatus().toString());
+            }
+            catch (CheckAuthHappened e)
+            {
+                // this is the expected case
+            }
+        }
+
+        public void testExpiredAssoc()
+        {
+            long earlier = Util.getTimeStamp() - 10;
+            String handle = "handle";
+
+            Association assoc = new Association(handle, new byte[20], earlier,
+                    1, "HMAC-SHA1");
+
+            store.storeAssociation(serverUrl, assoc);
+
+            Map query = new HashMap();
+            query.put("openid.mode", "id_res");
+            query.put("openid.return_to", returnTo);
+            query.put("openid.identity", serverId);
+            query.put("openid.assoc_handle", handle);
+            query.put("openid.sig", "whatever");
+            query.put("openid.signed", "mode");
+            query.put(Consumer.getNonceName(), nonce);
+
+            try
+            {
+                Response r = consumer.complete(query);
+                fail(r.getStatus().toString());
+            }
+            catch (CheckAuthHappened e)
+            {
+                // this is the expected case
+            }
+        }
+
+        public void testNewerAssoc()
+        {
+            long earlier = Util.getTimeStamp() - 10;
+            String goodHandle = "good handle";
+
+            Association goodAssoc = new Association(goodHandle, new byte[20],
+                    earlier, 1000, "HMAC-SHA1");
+
+            store.storeAssociation(serverUrl, goodAssoc);
+
+            String badHandle = "bad_handle";
+
+            Association badAssoc = new Association(badHandle, new byte[20],
+                    earlier + 5, 1000, "HMAC-SHA1");
+
+            store.storeAssociation(serverUrl, badAssoc);
+
+            store.storeNonce(nonce);
+
+            Map query = new HashMap();
+            query.put("openid.mode", "id_res");
+            query.put("openid.return_to", returnTo);
+            query.put("openid.identity", serverId);
+            query.put("openid.assoc_handle", goodHandle);
+            query.put(Consumer.getNonceName(), nonce);
+
+            goodAssoc.signMap(Arrays.asList(new String[] {"mode", "return_to",
+                    "identity"}), query);
+
+            Response r = consumer.complete(query);
+            assertEquals(StatusCode.SUCCESS, r.getStatus());
+            assertEquals(consumerId, ((SuccessResponse)r).getIdentityUrl());
+        }
+
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/DiscoveryManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/DiscoveryManagerTest.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/DiscoveryManagerTest.java (added)
+++ incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/DiscoveryManagerTest.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,254 @@
+package com.janrain.openid.consumer;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import com.janrain.url.FetchResponse;
+import com.janrain.url.HTTPFetcher;
+
+public class DiscoveryManagerTest extends TestCase
+{
+    static Map allDocs;
+    static
+    {
+        allDocs = new HashMap();
+        InputStream is = null;
+        try
+        {
+            is = DiscoveryManagerTest.class
+                    .getResourceAsStream("discoverydata.txt");
+            if (is == null)
+            {
+                fail("Unable to load test data");
+            }
+
+            InputStreamReader ir = new InputStreamReader(is, "UTF-8");
+            BufferedReader br = new BufferedReader(ir);
+
+            char [] chunk = new char[1024];
+            StringBuffer sb = new StringBuffer();
+
+            for (int l = br.read(chunk); l >= 0; l = br.read(chunk))
+            {
+                sb.append(chunk, 0, l);
+            }
+
+            String input = sb.toString();
+            String [] pages = input.split("\n\n\n");
+            for (int i = 0; i < pages.length; i++)
+            {
+                String [] parts = pages[i].split("\n", 2);
+                allDocs.put(parts[0], parts[1]);
+            }
+        }
+        catch (IOException e)
+        {
+            fail("This shouldn't have happened");
+        }
+        finally
+        {
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (IOException e)
+                {
+                    // nothing to do
+                }
+            }
+        }
+
+    }
+
+    static class DiscoveryFetcher extends HTTPFetcher
+    {
+        Map documents;
+        List fetchlog = new ArrayList();
+        String redirect;
+
+        public DiscoveryFetcher(Map documents)
+        {
+            this.documents = documents;
+        }
+
+        public FetchResponse fetch(String url, String body, Map headers)
+        {
+            fetchlog.add(new Object[] {url, body, headers});
+            String finalUrl = redirect == null ? url : redirect;
+
+            String [] o = (String [])documents.get(url);
+            int status = 200;
+            if (o == null)
+            {
+                status = 404;
+                o = new String[] {"text/plain", ""};
+            }
+            headers = new HashMap();
+            headers.put("content-type", o[0]);
+
+            try
+            {
+                byte [] bytes = o[1].getBytes("UTF-8");
+                return new FetchResponse(status, finalUrl, bytes, "UTF-8",
+                        headers);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                // still won't happen
+                return null;
+            }
+        }
+    }
+
+    private HTTPFetcher old;
+    private String idUrl = "http://someuser.unittest/";
+    private DiscoveryFetcher fetcher;
+    private Map documents;
+
+    protected void setUp() throws Exception
+    {
+        old = HTTPFetcher.getFetcher();
+        documents = new HashMap();
+        fetcher = new DiscoveryFetcher(documents);
+        HTTPFetcher.setFetcher(fetcher);
+    }
+
+    protected void tearDown() throws Exception
+    {
+        HTTPFetcher.setFetcher(old);
+        fetcher = null;
+    }
+
+    private void usedYadis(OpenIDService s)
+    {
+        assertTrue("Expected to use Yadis", s.isFromYadis());
+    }
+
+    private void notUsedYadis(OpenIDService s)
+    {
+        assertTrue("Expected to use old-style discover", !s.isFromYadis());
+    }
+
+    public void test404()
+    {
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertTrue(dm.isEmpty());
+    }
+
+    public void testNoYadis()
+    {
+        documents.put(idUrl, new String[] {"text/html",
+                (String)allDocs.get("openid_html")});
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertEquals(1, dm.size());
+        OpenIDService s = dm.getNext();
+        assertEquals(idUrl, s.getIdentityUrl());
+        assertEquals("http://www.myopenid.com/server", s.getServerUrl());
+        assertEquals("http://smoker.myopenid.com/", s.getDelegate());
+        notUsedYadis(s);
+    }
+
+    public void testNoOpenID()
+    {
+        documents.put(idUrl, new String[] {"text/plain", "junk"});
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertTrue(dm.isEmpty());
+    }
+
+    public void testYadis()
+    {
+        documents.put(idUrl, new String[] {"application/xrds+xml",
+                (String)allDocs.get("yadis_2entries")});
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertEquals(2, dm.size());
+
+        OpenIDService s1 = dm.getNext();
+        assertEquals(idUrl, s1.getIdentityUrl());
+        assertEquals("http://www.myopenid.com/server", s1.getServerUrl());
+        usedYadis(s1);
+
+        OpenIDService s2 = dm.getNext();
+        assertEquals(idUrl, s2.getIdentityUrl());
+        assertEquals("http://www.livejournal.com/openid/server.bml", s2
+                .getServerUrl());
+
+        usedYadis(s2);
+    }
+
+    public void testRedirect()
+    {
+        String expectedFinalUrl = "http://elsewhere.unittest/";
+        fetcher.redirect = expectedFinalUrl;
+
+        documents.put(idUrl, new String[] {"text/html",
+                (String)allDocs.get("openid_html")});
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertEquals(1, dm.size());
+        OpenIDService s = dm.getNext();
+        assertEquals(expectedFinalUrl, s.getIdentityUrl());
+        assertEquals("http://www.myopenid.com/server", s.getServerUrl());
+        assertEquals("http://smoker.myopenid.com/", s.getDelegate());
+        notUsedYadis(s);
+    }
+
+    public void testEmptyList()
+    {
+        documents.put(idUrl, new String[] {"application/xrds+xml",
+                (String)allDocs.get("yadis_0entries")});
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertTrue(dm.isEmpty());
+    }
+
+    public void testEmptyListWithLegacy()
+    {
+        documents.put(idUrl, new String[] {"text/html",
+                (String)allDocs.get("openid_and_yadis_html")});
+
+        documents.put(idUrl + "xrds", new String[] {"application/xrds+xml",
+                (String)allDocs.get("yadis_0entries")});
+
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertEquals(1, dm.size());
+        OpenIDService s = dm.getNext();
+        assertEquals(idUrl, s.getIdentityUrl());
+        assertEquals("http://www.myopenid.com/server", s.getServerUrl());
+        notUsedYadis(s);
+    }
+
+    public void testYadisNoDelegate()
+    {
+        documents.put(idUrl, new String[] {"application/xrds+xml",
+                (String)allDocs.get("yadis_no_delegate")});
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertEquals(1, dm.size());
+        OpenIDService s = dm.getNext();
+        assertEquals(idUrl, s.getIdentityUrl());
+        assertEquals(idUrl, s.getDelegate());
+        assertEquals("http://www.myopenid.com/server", s.getServerUrl());
+        usedYadis(s);
+    }
+
+    public void testOpenIDNoDelegate()
+    {
+        documents.put(idUrl, new String[] {"text/html",
+                (String)allDocs.get("openid_html_no_delegate")});
+        DiscoveryManager dm = new DiscoveryManager(idUrl);
+        assertEquals(1, dm.size());
+        OpenIDService s = dm.getNext();
+        assertEquals(idUrl, s.getIdentityUrl());
+        assertEquals(idUrl, s.getDelegate());
+        assertEquals("http://www.myopenid.com/server", s.getServerUrl());
+        notUsedYadis(s);
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/LinkParserTest.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/LinkParserTest.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/LinkParserTest.java (added)
+++ incubator/heraldry/libraries/java/trunk/test/src/com/janrain/openid/consumer/LinkParserTest.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,236 @@
+/*
+ * LinkParserTest.java JUnit based test Created on December 20, 2005, 4:59 PM
+ */
+
+package com.janrain.openid.consumer;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * @author JanRain, Inc.
+ */
+public class LinkParserTest extends TestCase
+{
+
+    public LinkParserTest(String testName)
+    {
+        super(testName);
+    }
+
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite(LinkParserTest.class);
+
+        return suite;
+    }
+
+    private static class Link
+    {
+        public boolean optional;
+        public Map attrs = new HashMap();
+        public Map optionalAttrs = new HashMap();
+
+        public Link(String unparsed)
+        {
+            String [] parts = unparsed.split("\\s+");
+            optional = parts[0].equals("Link*:");
+            assertTrue(optional || parts[0].equals("Link:"));
+
+            for (int i = 1; i < parts.length; i++)
+            {
+                String [] kv = parts[i].split("=", -1);
+                Map addTo;
+                if (kv[0].endsWith("*"))
+                {
+                    addTo = optionalAttrs;
+                    kv[0] = kv[0].substring(0, kv[0].length() - 1);
+                }
+                else
+                {
+                    addTo = attrs;
+                }
+                addTo.put(kv[0], kv[1]);
+            }
+        }
+    }
+
+    private static class Case
+    {
+        public String desc;
+        public String markup;
+        public List links = new ArrayList();
+        public List optionalLinks = new ArrayList();
+
+        public Case(String unparsed)
+        {
+            String [] parts = unparsed.split("\n\n", 2);
+            String header = parts[0];
+            markup = parts[1];
+
+            String [] lines = header.split("\n");
+            desc = lines[0].substring(6);
+
+            for (int i = 1; i < lines.length; i++)
+            {
+                Link l = new Link(lines[i]);
+                if (l.optional)
+                {
+                    optionalLinks.add(l);
+                }
+                else
+                {
+                    links.add(l);
+                }
+            }
+        }
+
+        public static Case [] parseCases(String input)
+        {
+            String [] parts = input.split("\n\n\n", -1);
+
+            String [] testLine = parts[0].split("\n", 2)[0].split(": ");
+            assertEquals("Num Tests", testLine[0]);
+            Case [] result = new Case[Integer.parseInt(testLine[1])];
+
+            assertEquals(result.length + 2, parts.length);
+
+            for (int i = 1; i < parts.length - 1; i++)
+            {
+                result[i - 1] = new Case(parts[i]);
+            }
+
+            return result;
+        }
+
+        public void test()
+        {
+            System.out.println("test: " + desc);
+            List results = LinkParser.parseLinkAttrs(this.markup);
+
+            int index = 0;
+            int optindex = 0;
+
+            for (Iterator res = results.iterator(); res.hasNext();)
+            {
+                LinkParser.LinkTag actual = (LinkParser.LinkTag)res.next();
+
+                Link exp;
+                if (index < links.size())
+                {
+                    exp = (Link)links.get(index);
+                    if (linkEqualsFound(exp, actual))
+                    {
+                        index++;
+                        continue;
+                    }
+                }
+
+                boolean found = false;
+                while (optindex < optionalLinks.size())
+                {
+                    exp = (Link)optionalLinks.get(optindex);
+                    optindex++;
+                    if (linkEqualsFound(exp, actual))
+                    {
+                        found = true;
+                        break;
+                    }
+                }
+                assertTrue(found);
+            }
+            assertEquals(links.size(), index);
+        }
+
+        public boolean linkEqualsFound(Link l, LinkParser.LinkTag t)
+        {
+            Set keys = t.getAttributeNames();
+            int found = 0;
+            for (Iterator it = keys.iterator(); it.hasNext();)
+            {
+                String name = it.next().toString();
+                String value = t.getAttribute(name);
+
+                if (value.equals(l.attrs.get(name)))
+                {
+                    found++;
+                    continue;
+                }
+
+                if (!value.equals(l.optionalAttrs.get(name)))
+                {
+                    return false;
+                }
+            }
+            return (found == t.size());
+        }
+    }
+
+    /**
+     * Test of parseLinkAttrs method, of class
+     * com.janrain.openid.consumer.LinkParser.
+     */
+    public void testParseLinkAttrs()
+    {
+        System.out.println("testParseLinkAttrs");
+
+        InputStream is = null;
+        try
+        {
+            is = getClass().getResourceAsStream("linkparse.txt");
+            if (is == null)
+            {
+                fail("Unable to load test data");
+            }
+
+            InputStreamReader ir = new InputStreamReader(is, "UTF-8");
+            BufferedReader br = new BufferedReader(ir);
+
+            char [] chunk = new char[1024];
+            StringBuffer sb = new StringBuffer();
+
+            for (int l = br.read(chunk); l >= 0; l = br.read(chunk))
+            {
+                sb.append(chunk, 0, l);
+            }
+
+            String input = sb.toString();
+            Case [] cases = Case.parseCases(input);
+
+            for (int i = 0; i < cases.length; i++)
+            {
+                cases[i].test();
+            }
+        }
+        catch (IOException e)
+        {
+            fail("This shouldn't have happened");
+        }
+        finally
+        {
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (IOException e)
+                {
+                    // nothing to do
+                }
+            }
+        }
+    }
+}



Mime
View raw message