From 554fd8c5195424bdbcabf5de30fdc183aba391bd Mon Sep 17 00:00:00 2001
From: upstream source tree
Date: Sun, 15 Mar 2015 20:14:05 -0400
Subject: obtained gcc-4.6.4.tar.bz2 from upstream website; verified
gcc-4.6.4.tar.bz2.sig; imported gcc-4.6.4 source tree from verified upstream
tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
---
.../gnu/java/net/protocol/http/Authenticator.java | 58 ++
.../protocol/http/ByteArrayRequestBodyWriter.java | 106 +++
.../java/net/protocol/http/ChunkedInputStream.java | 223 +++++
.../gnu/java/net/protocol/http/Cookie.java | 161 ++++
.../gnu/java/net/protocol/http/CookieManager.java | 65 ++
.../gnu/java/net/protocol/http/Credentials.java | 87 ++
.../gnu/java/net/protocol/http/HTTPConnection.java | 897 +++++++++++++++++++++
.../gnu/java/net/protocol/http/HTTPDateFormat.java | 440 ++++++++++
.../java/net/protocol/http/HTTPURLConnection.java | 693 ++++++++++++++++
.../gnu/java/net/protocol/http/Handler.java | 72 ++
.../gnu/java/net/protocol/http/Headers.java | 424 ++++++++++
.../protocol/http/LimitedLengthInputStream.java | 216 +++++
.../gnu/java/net/protocol/http/Request.java | 857 ++++++++++++++++++++
.../java/net/protocol/http/RequestBodyWriter.java | 68 ++
.../gnu/java/net/protocol/http/Response.java | 223 +++++
.../net/protocol/http/ResponseHeaderHandler.java | 56 ++
.../net/protocol/http/SimpleCookieManager.java | 137 ++++
.../gnu/java/net/protocol/http/package.html | 76 ++
18 files changed, 4859 insertions(+)
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/Authenticator.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/ByteArrayRequestBodyWriter.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/ChunkedInputStream.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/Cookie.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/CookieManager.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/Credentials.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/HTTPConnection.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/HTTPDateFormat.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/HTTPURLConnection.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/Handler.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/Headers.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/LimitedLengthInputStream.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/Request.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/RequestBodyWriter.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/Response.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/ResponseHeaderHandler.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/SimpleCookieManager.java
create mode 100644 libjava/classpath/gnu/java/net/protocol/http/package.html
(limited to 'libjava/classpath/gnu/java/net/protocol/http')
diff --git a/libjava/classpath/gnu/java/net/protocol/http/Authenticator.java b/libjava/classpath/gnu/java/net/protocol/http/Authenticator.java
new file mode 100644
index 000000000..b4ee41e11
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/Authenticator.java
@@ -0,0 +1,58 @@
+/* Authenticator.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+/**
+ * Callback interface for managing authentication.
+ * @see Request#setAuthenticator
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public interface Authenticator
+{
+
+ /**
+ * Returns the credentials to supply for the given realm.
+ * @param realm the authentication realm
+ * @param attempt zero on first authentication attempt, increments on each
+ * unsuccessful attempt
+ */
+ Credentials getCredentials(String realm, int attempt);
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/ByteArrayRequestBodyWriter.java b/libjava/classpath/gnu/java/net/protocol/http/ByteArrayRequestBodyWriter.java
new file mode 100644
index 000000000..22a33ccd3
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/ByteArrayRequestBodyWriter.java
@@ -0,0 +1,106 @@
+/* ByteArrayRequestBodyWriter.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+/**
+ * A simple request body writer using a byte array.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class ByteArrayRequestBodyWriter
+ implements RequestBodyWriter
+{
+
+ /**
+ * The content.
+ */
+ protected byte[] content;
+
+ /**
+ * The position within the content at which the next read will occur.
+ */
+ protected int pos;
+
+ /**
+ * Constructs a new byte array request body writer with the specified
+ * content.
+ * @param content the content buffer
+ */
+ public ByteArrayRequestBodyWriter(byte[] content)
+ {
+ this.content = content;
+ pos = 0;
+ }
+
+ /**
+ * Returns the total number of bytes that will be written in a single pass
+ * by this writer.
+ */
+ public int getContentLength()
+ {
+ return content.length;
+ }
+
+ /**
+ * Initialises the writer.
+ * This will be called before each pass.
+ */
+ public void reset()
+ {
+ pos = 0;
+ }
+
+ /**
+ * Writes body content to the supplied buffer.
+ * @param buffer the content buffer
+ * @return the number of bytes written
+ */
+ public int write(byte[] buffer)
+ {
+ int len = content.length - pos;
+ len = (buffer.length < len) ? buffer.length : len;
+ if (len > -1)
+ {
+ System.arraycopy(content, pos, buffer, 0, len);
+ pos += len;
+ }
+ return len;
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/ChunkedInputStream.java b/libjava/classpath/gnu/java/net/protocol/http/ChunkedInputStream.java
new file mode 100644
index 000000000..33df0df95
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/ChunkedInputStream.java
@@ -0,0 +1,223 @@
+/* ChunkedInputStream.java --
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import gnu.java.lang.CPStringBuilder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ProtocolException;
+
+
+//
+// Note that we rely on the implemtation of skip() in the super class
+// (InputStream) calling our read methods to account for chunk headers
+// while skipping.
+//
+
+
+/**
+ * Input stream wrapper for the "chunked" transfer-coding.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class ChunkedInputStream
+ extends InputStream
+{
+ Headers headers;
+
+ /** The underlying stream. */
+ private InputStream in;
+
+ /** Size of the chunk we're reading. */
+ int size;
+ /** Number of bytes we've read in this chunk. */
+ int count;
+ /**
+ * True when we should read meta-information, false when we should
+ * read data.
+ */
+ boolean meta;
+ /** True when we've hit EOF. */
+ boolean eof;
+
+ /**
+ * Constructor.
+ * @param in the response socket input stream
+ * @param headers the headers to receive additional header lines
+ */
+ public ChunkedInputStream(InputStream in, Headers headers)
+ {
+ this.in = in;
+ this.headers = headers;
+ size = -1;
+ count = 0;
+ meta = true;
+ }
+
+ public int read()
+ throws IOException
+ {
+ byte[] buf = new byte[1];
+ int len = read(buf, 0, 1);
+ if (len == -1)
+ {
+ return -1;
+ }
+ return 0xff & buf[0];
+ }
+
+ public synchronized int read(byte[] buffer, int offset, int length)
+ throws IOException
+ {
+ if (eof)
+ {
+ return -1;
+ }
+ if (meta)
+ {
+ // Read chunk header
+ int c, last = 0;
+ boolean seenSemi = false;
+ CPStringBuilder buf = new CPStringBuilder();
+ do
+ {
+ c = in.read();
+ if (c == 0x3b) // ;
+ {
+ seenSemi = true;
+ }
+ else if (c == 0x0a && last == 0x0d) // CRLF
+ {
+ try
+ {
+ size = Integer.parseInt(buf.toString(), 16);
+ }
+ catch (NumberFormatException nfe)
+ {
+ IOException ioe = new IOException("Bad chunk header");
+ ioe.initCause(nfe);
+ // Unrecoverable. Don't try to read more.
+ in.close();
+ throw ioe;
+ }
+ break;
+ }
+ else if (!seenSemi && c >= 0x30)
+ {
+ buf.append ((char) c);
+ }
+ last = c;
+ }
+ while(c != -1);
+ count = 0;
+ meta = false;
+ }
+ if (size == 0)
+ {
+ // Read trailer
+ headers.parse(in);
+ eof = true;
+ return -1;
+ }
+ else
+ {
+ int canRead = Math.min(size - count, length);
+ int len = in.read(buffer, offset, canRead);
+ if (len == -1)
+ {
+ // This is an error condition but it isn't clear what we
+ // should do with it.
+ eof = true;
+ return -1;
+ }
+ count += len;
+ if (count == size)
+ {
+ // Read CRLF
+ int c1 = in.read();
+ int c2 = in.read();
+ if (c1 == -1 || c2 == -1)
+ {
+ // EOF before CRLF: bad, but ignore
+ eof = true;
+ return -1;
+ }
+ if (c1 != 0x0d || c2 != 0x0a)
+ {
+ throw new ProtocolException("expecting CRLF: " + c1 + "," + c2);
+ }
+ meta = true;
+ }
+ return len;
+ }
+ }
+
+ /**
+ * This method returns the number of bytes that can be read from
+ * this stream before a read might block. Even if the underlying
+ * InputStream has data available past the end of the current chunk,
+ * we have no way of knowing how large the next chunk header will
+ * be. So we cannot report available data past the current chunk.
+ *
+ * @return The number of bytes that can be read before a read might
+ * block
+ *
+ * @exception IOException If an error occurs
+ */
+ public int available() throws IOException
+ {
+ if (meta)
+ return 0;
+
+ return Math.min(in.available(), size - count);
+ }
+
+ /**
+ * This method closes the ChunkedInputStream by closing the underlying
+ * InputStream.
+ *
+ * @exception IOException If an error occurs
+ */
+ public void close() throws IOException
+ {
+ in.close();
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/Cookie.java b/libjava/classpath/gnu/java/net/protocol/http/Cookie.java
new file mode 100644
index 000000000..122a23f79
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/Cookie.java
@@ -0,0 +1,161 @@
+/* Cookie.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import gnu.java.lang.CPStringBuilder;
+
+import java.util.Date;
+
+/**
+ * An HTTP cookie, as specified in RFC 2109.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class Cookie
+{
+
+ /**
+ * The name of the cookie.
+ */
+ protected final String name;
+
+ /**
+ * The value of the cookie.
+ */
+ protected final String value;
+
+ /**
+ * Optional documentation of the intended use of the cookie.
+ */
+ protected final String comment;
+
+ /**
+ * The domain for which the cookie is valid.
+ */
+ protected final String domain;
+
+ /**
+ * Optional subset of URL paths within the domain for which the cookie is
+ * valid.
+ */
+ protected final String path;
+
+ /**
+ * Indicates that the user-agent should only use secure means to transmit
+ * this cookie to the server.
+ */
+ protected final boolean secure;
+
+ /**
+ * The date at which this cookie expires.
+ */
+ protected final Date expires;
+
+ public Cookie(String name, String value, String comment, String domain,
+ String path, boolean secure, Date expires)
+ {
+ this.name = name;
+ this.value = value;
+ this.comment = comment;
+ this.domain = domain;
+ this.path = path;
+ this.secure = secure;
+ this.expires = expires;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public String getComment()
+ {
+ return comment;
+ }
+
+ public String getDomain()
+ {
+ return domain;
+ }
+
+ public String getPath()
+ {
+ return path;
+ }
+
+ public boolean isSecure()
+ {
+ return secure;
+ }
+
+ public Date getExpiryDate()
+ {
+ return expires;
+ }
+
+ public String toString()
+ {
+ return toString(true, true);
+ }
+
+ public String toString(boolean showPath, boolean showDomain)
+ {
+ CPStringBuilder buf = new CPStringBuilder();
+ buf.append(name);
+ buf.append('=');
+ buf.append(value);
+ if (showPath)
+ {
+ buf.append("; $Path=");
+ buf.append(path);
+ }
+ if (showDomain)
+ {
+ buf.append("; $Domain=");
+ buf.append(domain);
+ }
+ return buf.toString();
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/CookieManager.java b/libjava/classpath/gnu/java/net/protocol/http/CookieManager.java
new file mode 100644
index 000000000..da3686689
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/CookieManager.java
@@ -0,0 +1,65 @@
+/* CookieManager.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+/**
+ * Cookie manager interface.
+ * If an application wants to handle cookies, they should implement this
+ * interface and register the instance with each HTTPConnection they use.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public interface CookieManager
+{
+
+ /**
+ * Stores a cookie in the cookie manager.
+ * @param cookie the cookie to store
+ */
+ void setCookie(Cookie cookie);
+
+ /**
+ * Retrieves the cookies matching the specified criteria.
+ * @param host the host name
+ * @param secure whether the connection is secure
+ * @param path the path to access
+ */
+ Cookie[] getCookies(String host, boolean secure, String path);
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/Credentials.java b/libjava/classpath/gnu/java/net/protocol/http/Credentials.java
new file mode 100644
index 000000000..f95b4b53c
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/Credentials.java
@@ -0,0 +1,87 @@
+/* Credentials.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+/**
+ * Represents a username/password combination that can be used to
+ * authenticate to an HTTP server.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class Credentials
+{
+
+ /**
+ * The username.
+ */
+ private String username;
+
+ /**
+ * The password.
+ */
+ private String password;
+
+ /**
+ * Constructor.
+ * @param username the username
+ * @param password the password
+ */
+ public Credentials(String username, String password)
+ {
+ this.username = username;
+ this.password = password;
+ }
+
+ /**
+ * Returns the username.
+ */
+ public String getUsername()
+ {
+ return username;
+ }
+
+ /**
+ * Returns the password.
+ */
+ public String getPassword()
+ {
+ return password;
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/HTTPConnection.java b/libjava/classpath/gnu/java/net/protocol/http/HTTPConnection.java
new file mode 100644
index 000000000..b96bf4c54
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/HTTPConnection.java
@@ -0,0 +1,897 @@
+/* HTTPConnection.java --
+ Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import gnu.classpath.SystemProperties;
+
+import gnu.java.lang.CPStringBuilder;
+import gnu.java.net.EmptyX509TrustManager;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+
+/**
+ * A connection to an HTTP server.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class HTTPConnection
+{
+
+ /**
+ * The default HTTP port.
+ */
+ public static final int HTTP_PORT = 80;
+
+ /**
+ * The default HTTPS port.
+ */
+ public static final int HTTPS_PORT = 443;
+
+ private static final String userAgent = SystemProperties.getProperty("http.agent");
+
+ /**
+ * The host name of the server to connect to.
+ */
+ protected final String hostname;
+
+ /**
+ * The port to connect to.
+ */
+ protected final int port;
+
+ /**
+ * Whether the connection should use transport level security (HTTPS).
+ */
+ protected final boolean secure;
+
+ /**
+ * The connection timeout for connecting the underlying socket.
+ */
+ protected final int connectionTimeout;
+
+ /**
+ * The read timeout for reads on the underlying socket.
+ */
+ protected final int timeout;
+
+ /**
+ * The host name of the proxy to connect to.
+ */
+ protected String proxyHostname;
+
+ /**
+ * The port on the proxy to connect to.
+ */
+ protected int proxyPort;
+
+ /**
+ * The major version of HTTP supported by this client.
+ */
+ protected int majorVersion;
+
+ /**
+ * The minor version of HTTP supported by this client.
+ */
+ protected int minorVersion;
+
+ private final List handshakeCompletedListeners;
+
+ /**
+ * The socket this connection communicates on.
+ */
+ protected Socket socket;
+
+ /**
+ * The SSL socket factory to use.
+ */
+ private SSLSocketFactory sslSocketFactory;
+
+ /**
+ * The socket input stream.
+ */
+ protected InputStream in;
+
+ /**
+ * The socket output stream.
+ */
+ protected OutputStream out;
+
+ /**
+ * Nonce values seen by this connection.
+ */
+ private Map nonceCounts;
+
+ /**
+ * The cookie manager for this connection.
+ */
+ protected CookieManager cookieManager;
+
+
+ /**
+ * The pool that this connection is a member of (if any).
+ */
+ private Pool pool;
+
+ /**
+ * Creates a new HTTP connection.
+ * @param hostname the name of the host to connect to
+ */
+ public HTTPConnection(String hostname)
+ {
+ this(hostname, HTTP_PORT, false, 0, 0);
+ }
+
+ /**
+ * Creates a new HTTP or HTTPS connection.
+ * @param hostname the name of the host to connect to
+ * @param secure whether to use a secure connection
+ */
+ public HTTPConnection(String hostname, boolean secure)
+ {
+ this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure, 0, 0);
+ }
+
+ /**
+ * Creates a new HTTP or HTTPS connection on the specified port.
+ * @param hostname the name of the host to connect to
+ * @param secure whether to use a secure connection
+ * @param connectionTimeout the connection timeout
+ * @param timeout the socket read timeout
+ */
+ public HTTPConnection(String hostname, boolean secure,
+ int connectionTimeout, int timeout)
+ {
+ this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure,
+ connectionTimeout, timeout);
+ }
+
+ /**
+ * Creates a new HTTP connection on the specified port.
+ * @param hostname the name of the host to connect to
+ * @param port the port on the host to connect to
+ */
+ public HTTPConnection(String hostname, int port)
+ {
+ this(hostname, port, false, 0, 0);
+ }
+
+ /**
+ * Creates a new HTTP or HTTPS connection on the specified port.
+ * @param hostname the name of the host to connect to
+ * @param port the port on the host to connect to
+ * @param secure whether to use a secure connection
+ */
+ public HTTPConnection(String hostname, int port, boolean secure)
+ {
+ this(hostname, port, secure, 0, 0);
+ }
+
+ /**
+ * Creates a new HTTP or HTTPS connection on the specified port.
+ * @param hostname the name of the host to connect to
+ * @param port the port on the host to connect to
+ * @param secure whether to use a secure connection
+ * @param connectionTimeout the connection timeout
+ * @param timeout the socket read timeout
+ *
+ * @throws IllegalArgumentException if either connectionTimeout or
+ * timeout less than zero.
+ */
+ public HTTPConnection(String hostname, int port, boolean secure,
+ int connectionTimeout, int timeout)
+ {
+ if (connectionTimeout < 0 || timeout < 0)
+ throw new IllegalArgumentException();
+
+ this.hostname = hostname;
+ this.port = port;
+ this.secure = secure;
+ this.connectionTimeout = connectionTimeout;
+ this.timeout = timeout;
+ majorVersion = minorVersion = 1;
+ handshakeCompletedListeners
+ = new ArrayList(2);
+ }
+
+ /**
+ * Returns the name of the host to connect to.
+ */
+ public String getHostName()
+ {
+ return hostname;
+ }
+
+ /**
+ * Returns the port on the host to connect to.
+ */
+ public int getPort()
+ {
+ return port;
+ }
+
+ /**
+ * Indicates whether to use a secure connection or not.
+ */
+ public boolean isSecure()
+ {
+ return secure;
+ }
+
+ /**
+ * Returns the HTTP version string supported by this connection.
+ * @see #majorVersion
+ * @see #minorVersion
+ */
+ public String getVersion()
+ {
+ return "HTTP/" + majorVersion + '.' + minorVersion;
+ }
+
+ /**
+ * Sets the HTTP version supported by this connection.
+ * @param majorVersion the major version
+ * @param minorVersion the minor version
+ */
+ public void setVersion(int majorVersion, int minorVersion)
+ {
+ if (majorVersion != 1)
+ {
+ throw new IllegalArgumentException("major version not supported: " +
+ majorVersion);
+ }
+ if (minorVersion < 0 || minorVersion > 1)
+ {
+ throw new IllegalArgumentException("minor version not supported: " +
+ minorVersion);
+ }
+ this.majorVersion = majorVersion;
+ this.minorVersion = minorVersion;
+ }
+
+ /**
+ * Directs this connection to use the specified proxy.
+ * @param hostname the proxy host name
+ * @param port the port on the proxy to connect to
+ */
+ public void setProxy(String hostname, int port)
+ {
+ proxyHostname = hostname;
+ proxyPort = port;
+ }
+
+ /**
+ * Indicates whether this connection is using an HTTP proxy.
+ */
+ public boolean isUsingProxy()
+ {
+ return (proxyHostname != null && proxyPort > 0);
+ }
+
+ /**
+ * Sets the cookie manager to use for this connection.
+ * @param cookieManager the cookie manager
+ */
+ public void setCookieManager(CookieManager cookieManager)
+ {
+ this.cookieManager = cookieManager;
+ }
+
+ /**
+ * Returns the cookie manager in use for this connection.
+ */
+ public CookieManager getCookieManager()
+ {
+ return cookieManager;
+ }
+
+ /**
+ * Manages a pool of HTTPConections. The pool will have a maximum
+ * size determined by the value of the maxConn parameter passed to
+ * the {@link #get} method. This value inevitably comes from the
+ * http.maxConnections system property. If the
+ * classpath.net.http.keepAliveTTL system property is set, that will
+ * be the maximum time (in seconds) that an idle connection will be
+ * maintained.
+ */
+ static class Pool
+ {
+ /**
+ * Singleton instance of the pool.
+ */
+ static Pool instance = new Pool();
+
+ /**
+ * The pool
+ */
+ final LinkedList connectionPool
+ = new LinkedList();
+
+ /**
+ * Maximum size of the pool.
+ */
+ int maxConnections;
+
+ /**
+ * If greater than zero, the maximum time a connection will remain
+ * int the pool.
+ */
+ int connectionTTL;
+
+ /**
+ * A thread that removes connections older than connectionTTL.
+ */
+ class Reaper
+ implements Runnable
+ {
+ public void run()
+ {
+ synchronized (Pool.this)
+ {
+ try
+ {
+ do
+ {
+ while (connectionPool.size() > 0)
+ {
+ long currentTime = System.currentTimeMillis();
+
+ HTTPConnection c =
+ (HTTPConnection)connectionPool.getFirst();
+
+ long waitTime = c.timeLastUsed
+ + connectionTTL - currentTime;
+
+ if (waitTime <= 0)
+ removeOldest();
+ else
+ try
+ {
+ Pool.this.wait(waitTime);
+ }
+ catch (InterruptedException _)
+ {
+ // Ignore the interrupt.
+ }
+ }
+ // After the pool is empty, wait TTL to see if it
+ // is used again. This is because in the
+ // situation where a single thread is making HTTP
+ // requests to the same server it can remove the
+ // connection from the pool before the Reaper has
+ // a chance to start. This would cause the Reaper
+ // to exit if it were not for this extra delay.
+ // The result would be starting a Reaper thread
+ // for each HTTP request. With the delay we get
+ // at most one Reaper created each TTL.
+ try
+ {
+ Pool.this.wait(connectionTTL);
+ }
+ catch (InterruptedException _)
+ {
+ // Ignore the interrupt.
+ }
+ }
+ while (connectionPool.size() > 0);
+ }
+ finally
+ {
+ reaper = null;
+ }
+ }
+ }
+ }
+
+ Reaper reaper;
+
+ /**
+ * Private constructor to ensure singleton.
+ */
+ private Pool()
+ {
+ }
+
+ /**
+ * Tests for a matching connection.
+ *
+ * @param c connection to match.
+ * @param h the host name.
+ * @param p the port.
+ * @param sec true if using https.
+ *
+ * @return true if c matches h, p, and sec.
+ */
+ private static boolean matches(HTTPConnection c,
+ String h, int p, boolean sec)
+ {
+ return h.equals(c.hostname) && (p == c.port) && (sec == c.secure);
+ }
+
+ /**
+ * Get a pooled HTTPConnection. If there is an existing idle
+ * connection to the requested server it is returned. Otherwise a
+ * new connection is created.
+ *
+ * @param host the name of the host to connect to
+ * @param port the port on the host to connect to
+ * @param secure whether to use a secure connection
+ *
+ * @return the HTTPConnection.
+ */
+ synchronized HTTPConnection get(String host,
+ int port,
+ boolean secure,
+ int connectionTimeout, int timeout)
+ {
+ String ttl =
+ SystemProperties.getProperty("classpath.net.http.keepAliveTTL");
+ connectionTTL = 10000;
+ if (ttl != null && ttl.length() > 0)
+ try
+ {
+ int v = 1000 * Integer.parseInt(ttl);
+ if (v >= 0)
+ connectionTTL = v;
+ }
+ catch (NumberFormatException _)
+ {
+ // Ignore.
+ }
+
+ String mc = SystemProperties.getProperty("http.maxConnections");
+ maxConnections = 5;
+ if (mc != null && mc.length() > 0)
+ try
+ {
+ int v = Integer.parseInt(mc);
+ if (v > 0)
+ maxConnections = v;
+ }
+ catch (NumberFormatException _)
+ {
+ // Ignore.
+ }
+
+ HTTPConnection c = null;
+
+ ListIterator it = connectionPool.listIterator(0);
+ while (it.hasNext())
+ {
+ HTTPConnection cc = (HTTPConnection)it.next();
+ if (matches(cc, host, port, secure))
+ {
+ c = cc;
+ it.remove();
+ // Update the timeout.
+ if (c.socket != null)
+ try
+ {
+ c.socket.setSoTimeout(timeout);
+ }
+ catch (SocketException _)
+ {
+ // Ignore.
+ }
+ break;
+ }
+ }
+ if (c == null)
+ {
+ c = new HTTPConnection(host, port, secure,
+ connectionTimeout, timeout);
+ c.setPool(this);
+ }
+ return c;
+ }
+
+ /**
+ * Put an idle HTTPConnection back into the pool. If this causes
+ * the pool to be come too large, the oldest connection is removed
+ * and closed.
+ *
+ */
+ synchronized void put(HTTPConnection c)
+ {
+ c.timeLastUsed = System.currentTimeMillis();
+ connectionPool.addLast(c);
+
+ // maxConnections must always be >= 1
+ while (connectionPool.size() >= maxConnections)
+ removeOldest();
+
+ if (connectionTTL > 0 && null == reaper) {
+ // If there is a connectionTTL, then the reaper has removed
+ // any stale connections, so we don't have to check for stale
+ // now. We do have to start a reaper though, as there is not
+ // one running now.
+ reaper = new Reaper();
+ Thread t = new Thread(reaper, "HTTPConnection.Reaper");
+ t.setDaemon(true);
+ t.start();
+ }
+ }
+
+ /**
+ * Remove the oldest connection from the pool and close it.
+ */
+ void removeOldest()
+ {
+ HTTPConnection cx = (HTTPConnection)connectionPool.removeFirst();
+ try
+ {
+ cx.closeConnection();
+ }
+ catch (IOException ioe)
+ {
+ // Ignore it. We are just cleaning up.
+ }
+ }
+ }
+
+ /**
+ * The number of times this HTTPConnection has be used via keep-alive.
+ */
+ int useCount;
+
+ /**
+ * If this HTTPConnection is in the pool, the time it was put there.
+ */
+ long timeLastUsed;
+
+ /**
+ * Set the connection pool that this HTTPConnection is a member of.
+ * If left unset or set to null, it will not be a member of any pool
+ * and will not be a candidate for reuse.
+ *
+ * @param p the pool.
+ */
+ void setPool(Pool p)
+ {
+ pool = p;
+ }
+
+ /**
+ * Signal that this HTTPConnection is no longer needed and can be
+ * returned to the connection pool.
+ *
+ */
+ void release()
+ {
+ if (pool != null)
+ {
+ useCount++;
+ pool.put(this);
+
+ }
+ else
+ {
+ // If there is no pool, just close.
+ try
+ {
+ closeConnection();
+ }
+ catch (IOException ioe)
+ {
+ // Ignore it. We are just cleaning up.
+ }
+ }
+ }
+
+ /**
+ * Creates a new request using this connection.
+ * @param method the HTTP method to invoke
+ * @param path the URI-escaped RFC2396 abs_path
with
+ * optional query part
+ */
+ public Request newRequest(String method, String path)
+ {
+ if (method == null || method.length() == 0)
+ {
+ throw new IllegalArgumentException("method must have non-zero length");
+ }
+ if (path == null || path.length() == 0)
+ {
+ path = "/";
+ }
+ Request ret = new Request(this, method, path);
+ if ((secure && port != HTTPS_PORT) ||
+ (!secure && port != HTTP_PORT))
+ {
+ ret.setHeader("Host", hostname + ":" + port);
+ }
+ else
+ {
+ ret.setHeader("Host", hostname);
+ }
+ ret.setHeader("User-Agent", userAgent);
+ ret.setHeader("Connection", "keep-alive");
+ ret.setHeader("Accept-Encoding",
+ "chunked;q=1.0, gzip;q=0.9, deflate;q=0.8, " +
+ "identity;q=0.6, *;q=0");
+ if (cookieManager != null)
+ {
+ Cookie[] cookies = cookieManager.getCookies(hostname, secure, path);
+ if (cookies != null && cookies.length > 0)
+ {
+ CPStringBuilder buf = new CPStringBuilder();
+ buf.append("$Version=1");
+ for (int i = 0; i < cookies.length; i++)
+ {
+ buf.append(',');
+ buf.append(' ');
+ buf.append(cookies[i].toString());
+ }
+ ret.setHeader("Cookie", buf.toString());
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Closes this connection.
+ */
+ public void close()
+ throws IOException
+ {
+ closeConnection();
+ }
+
+ /**
+ * Retrieves the socket associated with this connection.
+ * This creates the socket if necessary.
+ */
+ protected synchronized Socket getSocket()
+ throws IOException
+ {
+ if (socket == null)
+ {
+ String connectHostname = hostname;
+ int connectPort = port;
+ if (isUsingProxy())
+ {
+ connectHostname = proxyHostname;
+ connectPort = proxyPort;
+ }
+ socket = new Socket();
+ InetSocketAddress address =
+ new InetSocketAddress(connectHostname, connectPort);
+ if (connectionTimeout > 0)
+ {
+ socket.connect(address, connectionTimeout);
+ }
+ else
+ {
+ socket.connect(address);
+ }
+ if (timeout > 0)
+ {
+ socket.setSoTimeout(timeout);
+ }
+ if (secure)
+ {
+ try
+ {
+ SSLSocketFactory factory = getSSLSocketFactory();
+ SSLSocket ss =
+ (SSLSocket) factory.createSocket(socket, connectHostname,
+ connectPort, true);
+ String[] protocols = { "TLSv1", "SSLv3" };
+ ss.setEnabledProtocols(protocols);
+ ss.setUseClientMode(true);
+ synchronized (handshakeCompletedListeners)
+ {
+ if (!handshakeCompletedListeners.isEmpty())
+ {
+ for (Iterator i =
+ handshakeCompletedListeners.iterator();
+ i.hasNext(); )
+ {
+ HandshakeCompletedListener l =
+ (HandshakeCompletedListener) i.next();
+ ss.addHandshakeCompletedListener(l);
+ }
+ }
+ }
+ ss.startHandshake();
+ socket = ss;
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new IOException(e.getMessage());
+ }
+ }
+ in = socket.getInputStream();
+ in = new BufferedInputStream(in);
+ out = socket.getOutputStream();
+ out = new BufferedOutputStream(out);
+ }
+ return socket;
+ }
+
+ SSLSocketFactory getSSLSocketFactory()
+ throws GeneralSecurityException
+ {
+ if (sslSocketFactory == null)
+ {
+ TrustManager tm = new EmptyX509TrustManager();
+ SSLContext context = SSLContext.getInstance("SSL");
+ TrustManager[] trust = new TrustManager[] { tm };
+ context.init(null, trust, null);
+ sslSocketFactory = context.getSocketFactory();
+ }
+ return sslSocketFactory;
+ }
+
+ void setSSLSocketFactory(SSLSocketFactory factory)
+ {
+ sslSocketFactory = factory;
+ }
+
+ protected synchronized InputStream getInputStream()
+ throws IOException
+ {
+ if (socket == null)
+ {
+ getSocket();
+ }
+ return in;
+ }
+
+ protected synchronized OutputStream getOutputStream()
+ throws IOException
+ {
+ if (socket == null)
+ {
+ getSocket();
+ }
+ return out;
+ }
+
+ /**
+ * Closes the underlying socket, if any.
+ */
+ protected synchronized void closeConnection()
+ throws IOException
+ {
+ if (socket != null)
+ {
+ try
+ {
+ socket.close();
+ }
+ finally
+ {
+ socket = null;
+ }
+ }
+ }
+
+ /**
+ * Returns a URI representing the connection.
+ * This does not include any request path component.
+ */
+ protected String getURI()
+ {
+ CPStringBuilder buf = new CPStringBuilder();
+ buf.append(secure ? "https://" : "http://");
+ buf.append(hostname);
+ if (secure)
+ {
+ if (port != HTTPConnection.HTTPS_PORT)
+ {
+ buf.append(':');
+ buf.append(port);
+ }
+ }
+ else
+ {
+ if (port != HTTPConnection.HTTP_PORT)
+ {
+ buf.append(':');
+ buf.append(port);
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Get the number of times the specified nonce has been seen by this
+ * connection.
+ */
+ int getNonceCount(String nonce)
+ {
+ if (nonceCounts == null)
+ {
+ return 0;
+ }
+ return nonceCounts.get(nonce).intValue();
+ }
+
+ /**
+ * Increment the number of times the specified nonce has been seen.
+ */
+ void incrementNonce(String nonce)
+ {
+ int current = getNonceCount(nonce);
+ if (nonceCounts == null)
+ {
+ nonceCounts = new HashMap();
+ }
+ nonceCounts.put(nonce, new Integer(current + 1));
+ }
+
+ // -- Events --
+
+ void addHandshakeCompletedListener(HandshakeCompletedListener l)
+ {
+ synchronized (handshakeCompletedListeners)
+ {
+ handshakeCompletedListeners.add(l);
+ }
+ }
+ void removeHandshakeCompletedListener(HandshakeCompletedListener l)
+ {
+ synchronized (handshakeCompletedListeners)
+ {
+ handshakeCompletedListeners.remove(l);
+ }
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/HTTPDateFormat.java b/libjava/classpath/gnu/java/net/protocol/http/HTTPDateFormat.java
new file mode 100644
index 000000000..743f8e8e2
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/HTTPDateFormat.java
@@ -0,0 +1,440 @@
+/* HTTPDateFormat.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * HTTP date formatter and parser.
+ * Formats dates according to RFC 822 (updated by RFC 1123).
+ * Parses dates according to the above, or RFC 1036, or the
+ * ANSI C asctime()
format.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class HTTPDateFormat
+ extends DateFormat
+{
+
+ static final String[] DAYS_OF_WEEK = {
+ null, "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+
+ static final String[] MONTHS = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ public HTTPDateFormat()
+ {
+ calendar = new GregorianCalendar(TimeZone.getTimeZone ("GMT"));
+ numberFormat = new DecimalFormat();
+ }
+
+ /**
+ * Appends the textual value for the specified field to the given string
+ * buffer. This method should be avoided, use format(Date)
+ * instead.
+ * @param date the Date object
+ * @param buf the buffer to append to
+ * @param field the current field position
+ * @return the modified buffer
+ */
+ public StringBuffer format(Date date, StringBuffer buf,
+ FieldPosition field)
+ {
+ calendar.clear();
+ calendar.setTime(date);
+ buf.setLength(0);
+
+ // Day of week
+ buf.append(DAYS_OF_WEEK[calendar.get(Calendar.DAY_OF_WEEK)]);
+ buf.append(',');
+ buf.append(' ');
+
+ // Day of month
+ int day = calendar.get(Calendar.DAY_OF_MONTH);
+ buf.append(Character.forDigit(day / 10, 10));
+ buf.append(Character.forDigit(day % 10, 10));
+ buf.append(' ');
+
+ // Month
+ buf.append(MONTHS[calendar.get(Calendar.MONTH)]);
+ buf.append(' ');
+
+ // Year
+ int year = calendar.get(Calendar.YEAR);
+ if (year < 1000)
+ {
+ buf.append('0');
+ if (year < 100)
+ {
+ buf.append('0');
+ if (year < 10)
+ {
+ buf.append('0');
+ }
+ }
+ }
+ buf.append(Integer.toString(year));
+ buf.append(' ');
+
+ // Hour
+ int hour = calendar.get(Calendar.HOUR_OF_DAY);
+ buf.append(Character.forDigit(hour / 10, 10));
+ buf.append(Character.forDigit(hour % 10, 10));
+ buf.append(':');
+
+ // Minute
+ int minute = calendar.get(Calendar.MINUTE);
+ buf.append(Character.forDigit(minute / 10, 10));
+ buf.append(Character.forDigit(minute % 10, 10));
+ buf.append(':');
+
+ // Second
+ int second = calendar.get(Calendar.SECOND);
+ buf.append(Character.forDigit(second / 10, 10));
+ buf.append(Character.forDigit(second % 10, 10));
+ buf.append(' ');
+
+ // Timezone
+ // Get time offset in minutes
+ int zoneOffset =(calendar.get(Calendar.ZONE_OFFSET) +
+ calendar.get(Calendar.DST_OFFSET)) / 60000;
+
+ // Apply + or - appropriately
+ if (zoneOffset < 0)
+ {
+ zoneOffset = -zoneOffset;
+ buf.append('-');
+ }
+ else
+ {
+ buf.append('+');
+ }
+
+ // Set the 2 2-char fields as specified above
+ int tzhours = zoneOffset / 60;
+ buf.append(Character.forDigit(tzhours / 10, 10));
+ buf.append(Character.forDigit(tzhours % 10, 10));
+ int tzminutes = zoneOffset % 60;
+ buf.append(Character.forDigit(tzminutes / 10, 10));
+ buf.append(Character.forDigit(tzminutes % 10, 10));
+
+ field.setBeginIndex(0);
+ field.setEndIndex(buf.length());
+ return buf;
+ }
+
+ /**
+ * Parses the given date in the current TimeZone.
+ * @param text the formatted date to be parsed
+ * @param pos the current parse position
+ */
+ public Date parse(String text, ParsePosition pos)
+ {
+ int date, month, year, hour, minute, second;
+ String monthText;
+ int start = 0, end = -1;
+ int len = text.length();
+ calendar.clear();
+ pos.setIndex(start);
+ try
+ {
+ // Advance to date
+ if (Character.isLetter(text.charAt(start)))
+ {
+ start = skipNonWhitespace(text, start);
+ }
+ // Determine mode
+ switch(start)
+ {
+ case 3:
+ // asctime
+ start = skipWhitespace(text, start);
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ monthText = text.substring(start, end);
+ month = -1;
+ for (int i = 0; i < 12; i++)
+ {
+ if (MONTHS[i].equals(monthText))
+ {
+ month = i;
+ break;
+ }
+ }
+ if (month == -1)
+ {
+ pos.setErrorIndex(end);
+ return null;
+ }
+ // Advance to date
+ start = skipWhitespace(text, end + 1);
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ date = Integer.parseInt(text.substring(start, end));
+ // Advance to hour
+ start = skipWhitespace(text, end + 1);
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, ':');
+ hour = Integer.parseInt(text.substring(start, end));
+ // Advance to minute
+ start = end + 1;
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, ':');
+ minute = Integer.parseInt(text.substring(start, end));
+ // Advance to second
+ start = end + 1;
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ second = Integer.parseInt(text.substring(start, end));
+ // Advance to year
+ start = skipWhitespace(text, end + 1);
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ year = Integer.parseInt(text.substring(start, end));
+ break;
+ case 0:
+ case 4:
+ // rfc822
+ start = skipWhitespace(text, start);
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ date = Integer.parseInt(text.substring(start, end));
+ // Advance to month
+ start = skipWhitespace(text, end + 1);
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ monthText = text.substring(start, end);
+ month = -1;
+ for (int i = 0; i < 12; i++)
+ {
+ if (MONTHS[i].equals(monthText))
+ {
+ month = i;
+ break;
+ }
+ }
+ if (month == -1)
+ {
+ pos.setErrorIndex(end);
+ return null;
+ }
+ // Advance to year
+ start = skipWhitespace(text, end + 1);
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ year = Integer.parseInt(text.substring(start, end));
+ // Advance to hour
+ start = skipWhitespace(text, end + 1);
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, ':');
+ hour = Integer.parseInt(text.substring(start, end));
+ // Advance to minute
+ start = end + 1;
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, ':');
+ minute = Integer.parseInt(text.substring(start, end));
+ // Advance to second
+ start = end + 1;
+ pos.setIndex(start);
+ end = start + 1;
+ while (end < len && !Character.isWhitespace(text.charAt(end)))
+ {
+ end++;
+ }
+ second = Integer.parseInt(text.substring(start, end));
+ break;
+ default:
+ // rfc850(obsolete)
+ start = skipWhitespace(text, start);
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, '-');
+ date = Integer.parseInt(text.substring(start, end));
+ // Advance to month
+ start = end + 1;
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, '-');
+ monthText = text.substring(start, end);
+ month = -1;
+ for (int i = 0; i < 12; i++)
+ {
+ if (MONTHS[i].equals(monthText))
+ {
+ month = i;
+ break;
+ }
+ }
+ if (month == -1)
+ {
+ pos.setErrorIndex(end);
+ return null;
+ }
+ // Advance to year
+ start = end + 1;
+ pos.setIndex(start);
+ end = skipNonWhitespace(text, start + 1);
+ year = 1900 + Integer.parseInt(text.substring(start, end));
+ // Advance to hour
+ start = skipWhitespace(text, end + 1);
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, ':');
+ hour = Integer.parseInt(text.substring(start, end));
+ // Advance to minute
+ start = end + 1;
+ pos.setIndex(start);
+ end = skipTo(text, start + 1, ':');
+ minute = Integer.parseInt(text.substring(start, end));
+ // Advance to second
+ start = end + 1;
+ pos.setIndex(start);
+ end = start + 1;
+ while (end < len && !Character.isWhitespace(text.charAt(end)))
+ {
+ end++;
+ }
+ second = Integer.parseInt(text.substring(start, end));
+ }
+
+ calendar.set(Calendar.YEAR, year);
+ calendar.set(Calendar.MONTH, month);
+ calendar.set(Calendar.DAY_OF_MONTH, date);
+ calendar.set(Calendar.HOUR, hour);
+ calendar.set(Calendar.MINUTE, minute);
+ calendar.set(Calendar.SECOND, second);
+
+ if (end != len)
+ {
+ // Timezone
+ start = skipWhitespace(text, end + 1);
+ end = start + 1;
+ while (end < len && !Character.isWhitespace(text.charAt(end)))
+ {
+ end++;
+ }
+ char pm = text.charAt(start);
+ if (Character.isLetter(pm))
+ {
+ TimeZone tz =
+ TimeZone.getTimeZone(text.substring(start, end));
+ calendar.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
+ }
+ else
+ {
+ int zoneOffset = 0;
+ zoneOffset += 600 * Character.digit(text.charAt(++start), 10);
+ zoneOffset += 60 * Character.digit(text.charAt(++start), 10);
+ zoneOffset += 10 * Character.digit(text.charAt(++start), 10);
+ zoneOffset += Character.digit(text.charAt(++start), 10);
+ zoneOffset *= 60000; // minutes -> ms
+ if ('-' == pm)
+ {
+ zoneOffset = -zoneOffset;
+ }
+ calendar.set(Calendar.ZONE_OFFSET, zoneOffset);
+ }
+ }
+ pos.setIndex(end);
+
+ return calendar.getTime();
+ }
+ catch (NumberFormatException e)
+ {
+ pos.setErrorIndex(Math.max(start, end));
+ }
+ catch (StringIndexOutOfBoundsException e)
+ {
+ pos.setErrorIndex(Math.max(start, end));
+ }
+ return null;
+ }
+
+ private int skipWhitespace(String text, int pos)
+ {
+ while(Character.isWhitespace(text.charAt(pos)))
+ {
+ pos++;
+ }
+ return pos;
+ }
+
+ private int skipNonWhitespace(String text, int pos)
+ {
+ while(!Character.isWhitespace(text.charAt(pos)))
+ {
+ pos++;
+ }
+ return pos;
+ }
+
+ private int skipTo(String text, int pos, char c)
+ {
+ while(text.charAt(pos) != c)
+ {
+ pos++;
+ }
+ return pos;
+ }
+
+ /**
+ * Don't allow setting the calendar.
+ */
+ public void setCalendar(Calendar newCalendar)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Don't allow setting the NumberFormat.
+ */
+ public void setNumberFormat(NumberFormat newNumberFormat)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/HTTPURLConnection.java b/libjava/classpath/gnu/java/net/protocol/http/HTTPURLConnection.java
new file mode 100644
index 000000000..9ba5c4793
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/HTTPURLConnection.java
@@ -0,0 +1,693 @@
+/* HTTPURLConnection.java --
+ Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import gnu.classpath.SystemProperties;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HandshakeCompletedEvent;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * A URLConnection that uses the HTTPConnection class.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class HTTPURLConnection
+ extends HttpsURLConnection
+ implements HandshakeCompletedListener
+{
+ /*
+ * The underlying connection.
+ */
+ private HTTPConnection connection;
+
+ // These are package private for use in anonymous inner classes.
+ String proxyHostname;
+ int proxyPort = -1;
+ String agent;
+ boolean keepAlive;
+
+ private Request request;
+ private Headers requestHeaders;
+ private ByteArrayOutputStream requestSink;
+ private boolean requestMethodSetExplicitly;
+
+ private Response response;
+ private InputStream responseSink;
+ private InputStream errorSink;
+
+ private HandshakeCompletedEvent handshakeEvent;
+
+ /**
+ * Constructor.
+ * @param url the URL
+ */
+ public HTTPURLConnection(URL url)
+ throws IOException
+ {
+ super(url);
+ requestHeaders = new Headers();
+ String proxy = SystemProperties.getProperty("http.proxyHost");
+ if (proxy != null && proxy.length() > 0)
+ {
+ String port = SystemProperties.getProperty("http.proxyPort");
+ if (port != null && port.length() > 0)
+ {
+ try
+ {
+ proxyPort = Integer.parseInt(port);
+ proxyHostname = proxy;
+ }
+ catch (NumberFormatException _)
+ {
+ // Ignore.
+ }
+ }
+ }
+ agent = SystemProperties.getProperty("http.agent");
+ String ka = SystemProperties.getProperty("http.keepAlive");
+ keepAlive = !(ka != null && "false".equals(ka));
+ }
+
+ public void connect()
+ throws IOException
+ {
+ if (connected)
+ {
+ return;
+ }
+ String protocol = url.getProtocol();
+ boolean secure = "https".equals(protocol);
+ String host = url.getHost();
+ int port = url.getPort();
+ if (port < 0)
+ {
+ port = secure ? HTTPConnection.HTTPS_PORT :
+ HTTPConnection.HTTP_PORT;
+ }
+ String file = url.getFile();
+ String username = url.getUserInfo();
+ String password = null;
+ if (username != null)
+ {
+ int ci = username.indexOf(':');
+ if (ci != -1)
+ {
+ password = username.substring(ci + 1);
+ username = username.substring(0, ci);
+ }
+ }
+ final Credentials creds = (username == null) ? null :
+ new Credentials (username, password);
+
+ if ("POST".equals(method))
+ {
+ String contentType = requestHeaders.getValue("Content-Type");
+ if (null == contentType)
+ requestHeaders.addValue("Content-Type",
+ "application/x-www-form-urlencoded");
+ }
+
+ boolean retry;
+ do
+ {
+ retry = false;
+ if (connection == null)
+ {
+ connection = getConnection(host, port, secure);
+ if (secure)
+ {
+ SSLSocketFactory factory = getSSLSocketFactory();
+ // FIXME: use the verifier
+ // HostnameVerifier verifier = getHostnameVerifier();
+ if (factory != null)
+ {
+ connection.setSSLSocketFactory(factory);
+ }
+ connection.addHandshakeCompletedListener(this);
+ // TODO verifier
+ }
+ }
+ if (proxyHostname != null)
+ {
+ if (proxyPort < 0)
+ {
+ proxyPort = secure ? HTTPConnection.HTTPS_PORT :
+ HTTPConnection.HTTP_PORT;
+ }
+ connection.setProxy(proxyHostname, proxyPort);
+ }
+ try
+ {
+ request = connection.newRequest(method, file);
+ if (!keepAlive)
+ {
+ request.setHeader("Connection", "close");
+ }
+ if (agent != null)
+ {
+ request.setHeader("User-Agent", agent);
+ }
+ request.getHeaders().putAll(requestHeaders);
+ if (requestSink != null)
+ {
+ byte[] content = requestSink.toByteArray();
+ RequestBodyWriter writer = new ByteArrayRequestBodyWriter(content);
+ request.setRequestBodyWriter(writer);
+ }
+ if (creds != null)
+ {
+ request.setAuthenticator(new Authenticator() {
+ public Credentials getCredentials(String realm, int attempts)
+ {
+ return (attempts < 2) ? creds : null;
+ }
+ });
+ }
+ response = request.dispatch();
+ }
+ catch (IOException ioe)
+ {
+ if (connection.useCount > 0)
+ {
+ // Connection re-use failed: Try a new connection.
+ try
+ {
+ connection.close();
+ }
+ catch (IOException _)
+ {
+ // Ignore.
+ }
+ connection = null;
+ retry = true;
+ continue;
+ }
+ else
+ {
+ // First time the connection was used: Hard failure.
+ throw ioe;
+ }
+ }
+
+ if (response.isRedirect() && getInstanceFollowRedirects())
+ {
+ // Read the response body, if there is one. If the
+ // redirect points us back at the same server, we will use
+ // the cached connection, so we must make sure there is no
+ // pending data in it.
+ InputStream body = response.getBody();
+ if (body != null)
+ {
+ byte[] ignore = new byte[1024];
+ while (true)
+ {
+ int n = body.read(ignore, 0, ignore.length);
+ if (n == -1)
+ break;
+ }
+ }
+
+ // Follow redirect
+ String location = response.getHeader("Location");
+ if (location != null)
+ {
+ String connectionUri = connection.getURI();
+ int start = connectionUri.length();
+ if (location.startsWith(connectionUri) &&
+ location.charAt(start) == '/')
+ {
+ file = location.substring(start);
+ retry = true;
+ }
+ else if (location.startsWith("http:"))
+ {
+ connection.close();
+ connection = null;
+ secure = false;
+ start = 7;
+ int end = location.indexOf('/', start);
+ if (end == -1)
+ end = location.length();
+ host = location.substring(start, end);
+ int ci = host.lastIndexOf(':');
+ if (ci != -1)
+ {
+ port = Integer.parseInt(host.substring (ci + 1));
+ host = host.substring(0, ci);
+ }
+ else
+ {
+ port = HTTPConnection.HTTP_PORT;
+ }
+ file = location.substring(end);
+ retry = true;
+ }
+ else if (location.startsWith("https:"))
+ {
+ connection.close();
+ connection = null;
+ secure = true;
+ start = 8;
+ int end = location.indexOf('/', start);
+ if (end == -1)
+ end = location.length();
+ host = location.substring(start, end);
+ int ci = host.lastIndexOf(':');
+ if (ci != -1)
+ {
+ port = Integer.parseInt(host.substring (ci + 1));
+ host = host.substring(0, ci);
+ }
+ else
+ {
+ port = HTTPConnection.HTTPS_PORT;
+ }
+ file = location.substring(end);
+ retry = true;
+ }
+ else if (location.length() > 0)
+ {
+ // Malformed absolute URI, treat as file part of URI
+ if (location.charAt(0) == '/')
+ {
+ // Absolute path
+ file = location;
+ }
+ else
+ {
+ // Relative path
+ int lsi = file.lastIndexOf('/');
+ file = (lsi == -1) ? "/" : file.substring(0, lsi + 1);
+ file += location;
+ }
+ retry = true;
+ }
+ }
+ }
+ else
+ {
+ responseSink = response.getBody();
+
+ if (response.isError())
+ errorSink = responseSink;
+ }
+ }
+ while (retry);
+ connected = true;
+ }
+
+ /**
+ * Returns a connection, from the pool if necessary.
+ */
+ HTTPConnection getConnection(String host, int port, boolean secure)
+ throws IOException
+ {
+ HTTPConnection connection;
+ if (keepAlive)
+ {
+ connection = HTTPConnection.Pool.instance.get(host, port, secure,
+ getConnectTimeout(),
+ getReadTimeout());
+ }
+ else
+ {
+ connection = new HTTPConnection(host, port, secure,
+ getConnectTimeout(), getReadTimeout());
+ }
+ return connection;
+ }
+
+ public void disconnect()
+ {
+ if (connection != null)
+ {
+ try
+ {
+ connection.close();
+ }
+ catch (IOException e)
+ {
+ }
+ }
+ }
+
+ public boolean usingProxy()
+ {
+ return (proxyHostname != null);
+ }
+
+ /**
+ * Overrides the corresponding method in HttpURLConnection to permit
+ * arbitrary methods, as long as they're valid ASCII alphabetic
+ * characters. This is to permit WebDAV and other HTTP extensions to
+ * function.
+ * @param method the method
+ */
+ public void setRequestMethod(String method)
+ throws ProtocolException
+ {
+ if (connected)
+ {
+ throw new ProtocolException("Already connected");
+ }
+ // Validate
+ method = method.toUpperCase();
+ int len = method.length();
+ if (len == 0)
+ {
+ throw new ProtocolException("Empty method name");
+ }
+ for (int i = 0; i < len; i++)
+ {
+ char c = method.charAt(i);
+ if (c < 0x41 || c > 0x5a)
+ {
+ throw new ProtocolException("Illegal character '" + c +
+ "' at index " + i);
+ }
+ }
+ // OK
+ this.method = method;
+ requestMethodSetExplicitly = true;
+ }
+
+ public String getRequestProperty(String key)
+ {
+ return requestHeaders.getValue(key);
+ }
+
+ public Map> getRequestProperties()
+ {
+ if (connected)
+ throw new IllegalStateException("Already connected");
+
+ Map> m = requestHeaders.getAsMap();
+ return Collections.unmodifiableMap(m);
+ }
+
+ public void setRequestProperty(String key, String value)
+ {
+ super.setRequestProperty(key, value);
+
+ requestHeaders.put(key, value);
+ }
+
+ public void addRequestProperty(String key, String value)
+ {
+ super.addRequestProperty(key, value);
+ requestHeaders.addValue(key, value);
+ }
+
+ public OutputStream getOutputStream()
+ throws IOException
+ {
+ if (connected)
+ {
+ throw new ProtocolException("Already connected");
+ }
+ if (!doOutput)
+ {
+ throw new ProtocolException("doOutput is false");
+ }
+ else if (!requestMethodSetExplicitly)
+ {
+ /*
+ * Silently change the method to POST if no method was set
+ * explicitly. This is due to broken applications depending on this
+ * behaviour (Apache XMLRPC for one).
+ */
+ method = "POST";
+ }
+ if (requestSink == null)
+ {
+ requestSink = new ByteArrayOutputStream();
+ }
+ return requestSink;
+ }
+
+ // -- Response --
+
+ public InputStream getInputStream()
+ throws IOException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+ if (!doInput)
+ {
+ throw new ProtocolException("doInput is false");
+ }
+
+ if (response.isError())
+ {
+ int code = response.getCode();
+ if (code == 404 || code == 410)
+ throw new FileNotFoundException(url.toString());
+
+ throw new IOException("Server returned HTTP response code " + code
+ + " for URL " + url.toString());
+ }
+
+ return responseSink;
+ }
+
+ public InputStream getErrorStream()
+ {
+ return errorSink;
+ }
+
+ public Map> getHeaderFields()
+ {
+ if (!connected)
+ {
+ try
+ {
+ connect();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+ Map> m = response.getHeaders().getAsMap();
+ m.put(null, Collections.singletonList(getStatusLine(response)));
+ return Collections.unmodifiableMap(m);
+ }
+
+ String getStatusLine(Response response)
+ {
+ return "HTTP/" + response.getMajorVersion() +
+ "." + response.getMinorVersion() +
+ " " + response.getCode() +
+ " " + response.getMessage();
+ }
+
+ public String getHeaderField(int index)
+ {
+ if (!connected)
+ {
+ try
+ {
+ connect();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+ if (index == 0)
+ {
+ return getStatusLine(response);
+ }
+ return response.getHeaders().getHeaderValue(index - 1);
+ }
+
+ public String getHeaderFieldKey(int index)
+ {
+ if (!connected)
+ {
+ try
+ {
+ connect();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+ // index of zero is the status line.
+ return response.getHeaders().getHeaderName(index - 1);
+ }
+
+ public String getHeaderField(String name)
+ {
+ if (!connected)
+ {
+ try
+ {
+ connect();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+ return response.getHeader(name);
+ }
+
+ public long getHeaderFieldDate(String name, long def)
+ {
+ if (!connected)
+ {
+ try
+ {
+ connect();
+ }
+ catch (IOException e)
+ {
+ return def;
+ }
+ }
+ Date date = response.getDateHeader(name);
+ return (date == null) ? def : date.getTime();
+ }
+
+ public String getContentType()
+ {
+ return getHeaderField("Content-Type");
+ }
+
+ public int getResponseCode()
+ throws IOException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+ return response.getCode();
+ }
+
+ public String getResponseMessage()
+ throws IOException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+ return response.getMessage();
+ }
+
+ // -- HTTPS specific --
+
+ public String getCipherSuite()
+ {
+ if (!connected)
+ {
+ throw new IllegalStateException("not connected");
+ }
+ return handshakeEvent.getCipherSuite();
+ }
+
+ public Certificate[] getLocalCertificates()
+ {
+ if (!connected)
+ {
+ throw new IllegalStateException("not connected");
+ }
+ return handshakeEvent.getLocalCertificates();
+ }
+
+ public Certificate[] getServerCertificates()
+ throws SSLPeerUnverifiedException
+ {
+ if (!connected)
+ {
+ throw new IllegalStateException("not connected");
+ }
+ return handshakeEvent.getPeerCertificates();
+ }
+
+ // HandshakeCompletedListener
+
+ public void handshakeCompleted(HandshakeCompletedEvent event)
+ {
+ handshakeEvent = event;
+ }
+
+ /**
+ * Set the read timeout, in milliseconds, or zero if the timeout
+ * is to be considered infinite.
+ *
+ * Overloaded.
+ *
+ */
+ public void setReadTimeout(int timeout)
+ throws IllegalArgumentException
+ {
+ super.setReadTimeout(timeout);
+ if (connection == null)
+ return;
+ try
+ {
+ connection.getSocket().setSoTimeout(timeout);
+ }
+ catch (IOException se)
+ {
+ // Ignore socket exceptions.
+ }
+ }
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/Handler.java b/libjava/classpath/gnu/java/net/protocol/http/Handler.java
new file mode 100644
index 000000000..30810321d
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/Handler.java
@@ -0,0 +1,72 @@
+/* Handler.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * An HTTP URL stream handler.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class Handler
+ extends URLStreamHandler
+{
+
+ /**
+ * Returns the default HTTP port (80).
+ */
+ protected int getDefaultPort()
+ {
+ return HTTPConnection.HTTP_PORT;
+ }
+
+ /**
+ * Returns an HTTPURLConnection for the given URL.
+ */
+ public URLConnection openConnection(URL url)
+ throws IOException
+ {
+ return new HTTPURLConnection(url);
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/Headers.java b/libjava/classpath/gnu/java/net/protocol/http/Headers.java
new file mode 100644
index 000000000..faf5eb195
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/Headers.java
@@ -0,0 +1,424 @@
+/* Headers.java --
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import gnu.java.lang.CPStringBuilder;
+
+import gnu.java.net.LineInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.Iterable;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A collection of HTTP header names and associated values. The
+ * values are {@link ArrayList ArrayLists} of Strings. Retrieval of
+ * values is case insensitive. An iteration over the collection
+ * returns the header names in the order they were received.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ * @author David Daney (ddaney@avtrex.com)
+ */
+class Headers implements Iterable
+{
+ /**
+ * A list of HeaderElements
+ */
+ private final ArrayList headers
+ = new ArrayList();
+
+ /**
+ * The HTTP dateformat used to parse date header fields.
+ */
+ private static final DateFormat dateFormat = new HTTPDateFormat();
+
+ /**
+ * Class for a Header element consisting of
+ * a name and value String.
+ */
+ static class HeaderElement
+ {
+ String name;
+ String value;
+
+ HeaderElement(String name, String value)
+ {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ /**
+ * Default constructor.
+ */
+ public Headers()
+ {
+ // nothing to do
+ }
+
+ /**
+ * Return an Iterator over this collection of headers.
+ * Iterator.getNext() returns objects of type {@link HeaderElement}.
+ *
+ * @return the Iterator.
+ */
+ public Iterator iterator()
+ {
+ return headers.iterator();
+ }
+
+ /**
+ * Returns the value of the specified header as a string. If
+ * multiple values are present, the last one is returned.
+ *
+ * @param header the header name (case insensitive search)
+ * @return The header value or null
if not found.
+ */
+ public String getValue(String header)
+ {
+ for (int i = headers.size() - 1; i >= 0; i--)
+ {
+ HeaderElement e = headers.get(i);
+ if (e.name.equalsIgnoreCase(header))
+ {
+ return e.value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the value of the specified header as an integer. If
+ * multiple values are present, the last one is returned.
+ *
+ * @param header the header name (case insensitive search)
+ * @return The header value or -1
if not present or
+ * not an integer value.
+ */
+ public int getIntValue(String header)
+ {
+ String val = getValue(header);
+ if (val == null)
+ {
+ return -1;
+ }
+ try
+ {
+ return Integer.parseInt(val);
+ }
+ catch (NumberFormatException e)
+ {
+ // fall through
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the value of the specified header as a long. If
+ * multiple values are present, the last one is returned.
+ *
+ * @param header the header name (case insensitive search)
+ * @return The header value or -1
if not present or
+ * not a long value.
+ */
+ public long getLongValue(String header)
+ {
+ String val = getValue(header);
+ if (val == null)
+ {
+ return -1;
+ }
+ try
+ {
+ return Long.parseLong(val);
+ }
+ catch (NumberFormatException e)
+ {
+ // fall through
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the value of the specified header as a date. If
+ * multiple values are present, the last one is returned.
+ *
+ * @param header the header name (case insensitive search)
+ * @return The header value or null
if not present or
+ * not a date value.
+ */
+ public Date getDateValue(String header)
+ {
+ String val = getValue(header);
+ if (val == null)
+ {
+ return null;
+ }
+ try
+ {
+ return dateFormat.parse(val);
+ }
+ catch (ParseException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Add a header to this set of headers. If there is an existing
+ * header with the same name it's value is replaced with the new value.
+ * If multiple headers of the same name exist only the last one's value
+ * is replaced.
+ *
+ * @param name the header name
+ * @param value the header value
+ *
+ * @see #addValue(String, String)
+ */
+ public void put(String name, String value)
+ {
+ for (int i = headers.size() - 1; i >= 0; i--)
+ {
+ HeaderElement e = headers.get(i);
+ if (e.name.equalsIgnoreCase(name))
+ {
+ e.value = value;
+ return;
+ }
+ }
+
+ // nothing was replaced so add it as new HeaderElement
+ addValue(name, value);
+ }
+
+ /**
+ * Add all headers from a set of headers to this set. Any existing header
+ * with the same (case insensitive) name as one of the new headers will
+ * be overridden.
+ *
+ * @param o the headers to be added
+ */
+ public void putAll(Headers o)
+ {
+ for (Iterator it = o.iterator(); it.hasNext(); )
+ {
+ HeaderElement e = it.next();
+ remove(e.name);
+ addValue(e.name, e.value);
+ }
+ }
+
+ /**
+ * Remove a header from this set of headers. If there is more than
+ * one instance of a header of the given name, they are all removed.
+ *
+ * @param name the header name
+ */
+ public void remove(String name)
+ {
+ for (Iterator it = headers.iterator(); it.hasNext(); )
+ {
+ HeaderElement e = it.next();
+ if (e.name.equalsIgnoreCase(name))
+ it.remove();
+ }
+ }
+
+ /**
+ * Parse the specified InputStream, adding headers to this collection.
+ *
+ * @param in the InputStream.
+ * @throws IOException if I/O error occured.
+ */
+ public void parse(InputStream in)
+ throws IOException
+ {
+ LineInputStream lin = (in instanceof LineInputStream) ?
+ (LineInputStream) in : new LineInputStream(in);
+
+ String name = null;
+ CPStringBuilder value = new CPStringBuilder();
+ while (true)
+ {
+ String line = lin.readLine();
+ if (line == null)
+ {
+ if (name != null)
+ {
+ addValue(name, value.toString());
+ }
+ break;
+ }
+ int len = line.length();
+ if (len < 2)
+ {
+ if (name != null)
+ {
+ addValue(name, value.toString());
+ }
+ break;
+ }
+ char c1 = line.charAt(0);
+ if (c1 == ' ' || c1 == '\t')
+ {
+ // Continuation
+ int last = len - 1;
+ if (line.charAt(last) != '\r')
+ ++last;
+ value.append(line.substring(0, last));
+ }
+ else
+ {
+ if (name != null)
+ {
+ addValue(name, value.toString());
+ }
+
+ int di = line.indexOf(':');
+ name = line.substring(0, di);
+ value.setLength(0);
+ do
+ {
+ di++;
+ }
+ while (di < len && line.charAt(di) == ' ');
+ int last = len - 1;
+ if (line.charAt(last) != '\r')
+ ++last;
+ value.append(line.substring(di, last));
+ }
+ }
+ }
+
+
+ /**
+ * Add a header to this set of headers. If there is an existing
+ * header with the same name, it is not effected.
+ *
+ * @param name the header name
+ * @param value the header value
+ *
+ * @see #put(String, String)
+ */
+ public void addValue(String name, String value)
+ {
+ headers.add(headers.size(), new HeaderElement(name, value));
+ }
+
+ /**
+ * Get a new Map containing all the headers. The keys of the Map
+ * are Strings (the header names). The headers will be included
+ * case-sensitive in the map so that querying must be done with the
+ * correct case of the needed header name. The values of the Map are
+ * unmodifiable Lists containing Strings (the header values).
+ *
+ *
+ * The returned map is modifiable. Changing it will not effect this
+ * collection of Headers in any way.
+ *
+ * @return a Map containing all the headers.
+ */
+ public Map> getAsMap()
+ {
+ LinkedHashMap> m = new LinkedHashMap>();
+ for (Iterator it = headers.iterator(); it.hasNext(); )
+ {
+ HeaderElement e = it.next();
+ ArrayList l = (ArrayList)m.get(e.name);
+ if (l == null)
+ {
+ l = new ArrayList(1);
+ l.add(e.value);
+ m.put(e.name, l);
+ }
+ else
+ l.add(0, e.value);
+ }
+ for (Iterator>> it = m.entrySet().iterator(); it.hasNext(); )
+ {
+ Map.Entry> me = it.next();
+ List l = me.getValue();
+ me.setValue(Collections.unmodifiableList(l));
+ }
+ return m;
+ }
+
+ /**
+ * Get the name of the Nth header.
+ *
+ * @param i the header index.
+ *
+ * @return The header name, or null
if index outside of range.
+ *
+ * @see #getHeaderValue(int)
+ */
+ public String getHeaderName(int i)
+ {
+ if (i >= headers.size() || i < 0)
+ return null;
+
+ return headers.get(i).name;
+ }
+
+ /**
+ * Get the value of the Nth header.
+ *
+ * @param i the header index.
+ *
+ * @return the header value, or null
if index outside of range.
+ *
+ * @see #getHeaderName(int)
+ */
+ public String getHeaderValue(int i)
+ {
+ if (i >= headers.size() || i < 0)
+ return null;
+
+ return headers.get(i).value;
+ }
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/LimitedLengthInputStream.java b/libjava/classpath/gnu/java/net/protocol/http/LimitedLengthInputStream.java
new file mode 100644
index 000000000..568f830fd
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/LimitedLengthInputStream.java
@@ -0,0 +1,216 @@
+/* LimitedLengthInputStream.java --
+ Copyright (C) 2005, 2008 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStream that limits the total number of bytes that can be read
+ * from an underlying stream. In addition to limiting the number of
+ * bytes read, close() is not propagated to the underlying stream.
+ *
+ * @author David Daney (ddaney@avtrex.com)
+ */
+class LimitedLengthInputStream
+ extends InputStream
+{
+ private long remainingLen;
+ private boolean restrictLen;
+ private HTTPConnection connection;
+ private boolean eof;
+ private InputStream in;
+ private boolean doClose;
+
+ private void handleClose()
+ throws IOException
+ {
+ eof = true;
+
+ if (doClose)
+ in.close();
+ else
+ connection.release();
+
+ in = null;
+ connection = null;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param in the underlying stream
+ *
+ * @param maxLen the maximum number of bytes to read
+ *
+ * @param restrictLen if true the number of bytes that can be read
+ * from this stream will be limited to maxLen, otherwise the number
+ * of bytes is not restricted.
+ *
+ * @param con the HTTPConnection associated with this stream
+ *
+ * @param doClose if true con will be closed when finished reading,
+ * else it will be placed back in the connection pool.
+ *
+ */
+ LimitedLengthInputStream(InputStream in,
+ long maxLen,
+ boolean restrictLen,
+ HTTPConnection con,
+ boolean doClose)
+ throws IOException
+ {
+ this.in = in;
+ this.remainingLen = maxLen;
+ this.restrictLen = restrictLen;
+ this.connection = con;
+ this.doClose = doClose;
+
+ if (restrictLen)
+ {
+ if (maxLen < 0)
+ throw new IllegalArgumentException();
+ else if (maxLen == 0)
+ handleClose(); // Nothing to do, release the connection.
+ }
+ }
+
+ public synchronized int read()
+ throws IOException
+ {
+ if (eof)
+ return -1; // EOF
+
+ int r;
+
+ if (restrictLen)
+ {
+ r = in.read();
+ if (-1 != r)
+ remainingLen--;
+
+ if (0 == remainingLen)
+ handleClose();
+ }
+ else
+ {
+ r = in.read();
+ if (r == -1)
+ handleClose();
+ }
+
+ return r;
+ }
+
+ public int read(byte[] buffer)
+ throws IOException
+ {
+ return read(buffer, 0, buffer.length);
+ }
+
+ public synchronized int read(byte[] buffer, int offset, int length)
+ throws IOException
+ {
+ if (eof)
+ return -1; // EOF
+
+ if (restrictLen && length > remainingLen)
+ length = (int) remainingLen;
+
+ int r = in.read(buffer, offset, length);
+
+ if (-1 == r)
+ handleClose();
+
+ if (restrictLen && r > 0)
+ {
+ remainingLen -= r;
+ if (0 == remainingLen)
+ handleClose();
+ }
+ return r;
+ }
+
+ public synchronized long skip(long n)
+ throws IOException
+ {
+
+ if (eof)
+ return 0;
+
+ if (restrictLen && n > remainingLen)
+ n = remainingLen;
+
+ long r = in.skip(n);
+
+ if (restrictLen)
+ {
+ remainingLen -= r;
+ if (0 == remainingLen)
+ handleClose();
+ }
+ return r;
+ }
+
+ public synchronized int available()
+ throws IOException
+ {
+ if (eof)
+ return 0;
+
+ int a = in.available();
+ if (restrictLen && a > remainingLen)
+ a = (int)remainingLen;
+ return a;
+ }
+
+ public synchronized void close()
+ throws IOException
+ {
+ if (eof)
+ return;
+
+ // If we get to here, the stream was not fully read. Just throw
+ // it away.
+
+ doClose = true;
+
+ handleClose();
+ }
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/Request.java b/libjava/classpath/gnu/java/net/protocol/http/Request.java
new file mode 100644
index 000000000..534213eed
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/Request.java
@@ -0,0 +1,857 @@
+/* Request.java --
+ Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import gnu.java.lang.CPStringBuilder;
+import gnu.java.net.LineInputStream;
+import gnu.java.util.Base64;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ProtocolException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * A single HTTP request.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class Request
+{
+
+ /**
+ * The connection context in which this request is invoked.
+ */
+ protected final HTTPConnection connection;
+
+ /**
+ * The HTTP method to invoke.
+ */
+ protected final String method;
+
+ /**
+ * The path identifying the resource.
+ * This string must conform to the abs_path definition given in RFC2396,
+ * with an optional "?query" part, and must be URI-escaped by the caller.
+ */
+ protected final String path;
+
+ /**
+ * The headers in this request.
+ */
+ protected final Headers requestHeaders;
+
+ /**
+ * The request body provider.
+ */
+ protected RequestBodyWriter requestBodyWriter;
+
+ /**
+ * Map of response header handlers.
+ */
+ protected Map responseHeaderHandlers;
+
+ /**
+ * The authenticator.
+ */
+ protected Authenticator authenticator;
+
+ /**
+ * Whether this request has been dispatched yet.
+ */
+ private boolean dispatched;
+
+ /**
+ * Constructor for a new request.
+ * @param connection the connection context
+ * @param method the HTTP method
+ * @param path the resource path including query part
+ */
+ protected Request(HTTPConnection connection, String method,
+ String path)
+ {
+ this.connection = connection;
+ this.method = method;
+ this.path = path;
+ requestHeaders = new Headers();
+ responseHeaderHandlers = new HashMap();
+ }
+
+ /**
+ * Returns the connection associated with this request.
+ * @see #connection
+ */
+ public HTTPConnection getConnection()
+ {
+ return connection;
+ }
+
+ /**
+ * Returns the HTTP method to invoke.
+ * @see #method
+ */
+ public String getMethod()
+ {
+ return method;
+ }
+
+ /**
+ * Returns the resource path.
+ * @see #path
+ */
+ public String getPath()
+ {
+ return path;
+ }
+
+ /**
+ * Returns the full request-URI represented by this request, as specified
+ * by HTTP/1.1.
+ */
+ public String getRequestURI()
+ {
+ return connection.getURI() + path;
+ }
+
+ /**
+ * Returns the headers in this request.
+ */
+ public Headers getHeaders()
+ {
+ return requestHeaders;
+ }
+
+ /**
+ * Returns the value of the specified header in this request.
+ * @param name the header name
+ */
+ public String getHeader(String name)
+ {
+ return requestHeaders.getValue(name);
+ }
+
+ /**
+ * Returns the value of the specified header in this request as an integer.
+ * @param name the header name
+ */
+ public int getIntHeader(String name)
+ {
+ return requestHeaders.getIntValue(name);
+ }
+
+ /**
+ * Returns the value of the specified header in this request as a date.
+ * @param name the header name
+ */
+ public Date getDateHeader(String name)
+ {
+ return requestHeaders.getDateValue(name);
+ }
+
+ /**
+ * Sets the specified header in this request.
+ * @param name the header name
+ * @param value the header value
+ */
+ public void setHeader(String name, String value)
+ {
+ requestHeaders.put(name, value);
+ }
+
+ /**
+ * Convenience method to set the entire request body.
+ * @param requestBody the request body content
+ */
+ public void setRequestBody(byte[] requestBody)
+ {
+ setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody));
+ }
+
+ /**
+ * Sets the request body provider.
+ * @param requestBodyWriter the handler used to obtain the request body
+ */
+ public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter)
+ {
+ this.requestBodyWriter = requestBodyWriter;
+ }
+
+ /**
+ * Sets a callback handler to be invoked for the specified header name.
+ * @param name the header name
+ * @param handler the handler to receive the value for the header
+ */
+ public void setResponseHeaderHandler(String name,
+ ResponseHeaderHandler handler)
+ {
+ responseHeaderHandlers.put(name, handler);
+ }
+
+ /**
+ * Sets an authenticator that can be used to handle authentication
+ * automatically.
+ * @param authenticator the authenticator
+ */
+ public void setAuthenticator(Authenticator authenticator)
+ {
+ this.authenticator = authenticator;
+ }
+
+ /**
+ * Dispatches this request.
+ * A request can only be dispatched once; calling this method a second
+ * time results in a protocol exception.
+ * @exception IOException if an I/O error occurred
+ * @return an HTTP response object representing the result of the operation
+ */
+ public Response dispatch()
+ throws IOException
+ {
+ if (dispatched)
+ {
+ throw new ProtocolException("request already dispatched");
+ }
+ final String CRLF = "\r\n";
+ final String HEADER_SEP = ": ";
+ final String US_ASCII = "US-ASCII";
+ final String version = connection.getVersion();
+ Response response;
+ int contentLength = -1;
+ boolean retry = false;
+ int attempts = 0;
+ boolean expectingContinue = false;
+ if (requestBodyWriter != null)
+ {
+ contentLength = requestBodyWriter.getContentLength();
+ String expect = getHeader("Expect");
+ if (expect != null && expect.equals("100-continue"))
+ {
+ expectingContinue = true;
+ }
+ else
+ {
+ setHeader("Content-Length", Integer.toString(contentLength));
+ }
+ }
+
+ try
+ {
+ // Loop while authentication fails or continue
+ do
+ {
+ retry = false;
+
+ // Get socket output and input streams
+ OutputStream out = connection.getOutputStream();
+
+ // Request line
+ String requestUri = path;
+ if (connection.isUsingProxy() &&
+ !"*".equals(requestUri) &&
+ !"CONNECT".equals(method))
+ {
+ requestUri = getRequestURI();
+ }
+ String line = method + ' ' + requestUri + ' ' + version + CRLF;
+ out.write(line.getBytes(US_ASCII));
+ // Request headers
+ for (Headers.HeaderElement elt : requestHeaders)
+ {
+ line = elt.name + HEADER_SEP + elt.value + CRLF;
+ out.write(line.getBytes(US_ASCII));
+ }
+ out.write(CRLF.getBytes(US_ASCII));
+ // Request body
+ if (requestBodyWriter != null && !expectingContinue)
+ {
+ byte[] buffer = new byte[4096];
+ int len;
+ int count = 0;
+
+ requestBodyWriter.reset();
+ do
+ {
+ len = requestBodyWriter.write(buffer);
+ if (len > 0)
+ {
+ out.write(buffer, 0, len);
+ }
+ count += len;
+ }
+ while (len > -1 && count < contentLength);
+ }
+ out.flush();
+ // Get response
+ while(true)
+ {
+ response = readResponse(connection.getInputStream());
+ int sc = response.getCode();
+ if (sc == 401 && authenticator != null)
+ {
+ if (authenticate(response, attempts++))
+ {
+ retry = true;
+ }
+ }
+ else if (sc == 100)
+ {
+ if (expectingContinue)
+ {
+ requestHeaders.remove("Expect");
+ setHeader("Content-Length",
+ Integer.toString(contentLength));
+ expectingContinue = false;
+ retry = true;
+ }
+ else
+ {
+ // A conforming server can send an unsoliceted
+ // Continue response but *should* not (RFC 2616
+ // sec 8.2.3). Ignore the bogus Continue
+ // response and get the real response that
+ // should follow
+ continue;
+ }
+ }
+ break;
+ }
+ }
+ while (retry);
+ }
+ catch (IOException e)
+ {
+ connection.close();
+ throw e;
+ }
+ return response;
+ }
+
+ Response readResponse(InputStream in)
+ throws IOException
+ {
+ String line;
+ int len;
+
+ // Read response status line
+ LineInputStream lis = new LineInputStream(in);
+
+ line = lis.readLine();
+ if (line == null)
+ {
+ throw new ProtocolException("Peer closed connection");
+ }
+ if (!line.startsWith("HTTP/"))
+ {
+ throw new ProtocolException(line);
+ }
+ len = line.length();
+ int start = 5, end = 6;
+ while (line.charAt(end) != '.')
+ {
+ end++;
+ }
+ int majorVersion = Integer.parseInt(line.substring(start, end));
+ start = end + 1;
+ end = start + 1;
+ while (line.charAt(end) != ' ')
+ {
+ end++;
+ }
+ int minorVersion = Integer.parseInt(line.substring(start, end));
+ start = end + 1;
+ end = start + 3;
+ int code = Integer.parseInt(line.substring(start, end));
+ String message = line.substring(end + 1, len - 1);
+ // Read response headers
+ Headers responseHeaders = new Headers();
+ responseHeaders.parse(lis);
+ notifyHeaderHandlers(responseHeaders);
+ InputStream body = null;
+
+ switch (code)
+ {
+ case 100:
+ break;
+ case 204:
+ case 205:
+ case 304:
+ body = createResponseBodyStream(responseHeaders, majorVersion,
+ minorVersion, in, false);
+ break;
+ default:
+ body = createResponseBodyStream(responseHeaders, majorVersion,
+ minorVersion, in, true);
+ }
+
+ // Construct response
+ Response ret = new Response(majorVersion, minorVersion, code,
+ message, responseHeaders, body);
+ return ret;
+ }
+
+ void notifyHeaderHandlers(Headers headers)
+ {
+ for (Headers.HeaderElement entry : headers)
+ {
+ // Handle Set-Cookie
+ if ("Set-Cookie".equalsIgnoreCase(entry.name))
+ handleSetCookie(entry.value);
+
+ ResponseHeaderHandler handler =
+ (ResponseHeaderHandler) responseHeaderHandlers.get(entry.name);
+ if (handler != null)
+ handler.setValue(entry.value);
+ }
+ }
+
+ private InputStream createResponseBodyStream(Headers responseHeaders,
+ int majorVersion,
+ int minorVersion,
+ InputStream in,
+ boolean mayHaveBody)
+ throws IOException
+ {
+ long contentLength = -1;
+
+ // Persistent connections are the default in HTTP/1.1
+ boolean doClose = "close".equalsIgnoreCase(getHeader("Connection")) ||
+ "close".equalsIgnoreCase(responseHeaders.getValue("Connection")) ||
+ (connection.majorVersion == 1 && connection.minorVersion == 0) ||
+ (majorVersion == 1 && minorVersion == 0);
+
+ String transferCoding = responseHeaders.getValue("Transfer-Encoding");
+ if ("HEAD".equals(method) || !mayHaveBody)
+ {
+ // Special case no body.
+ in = new LimitedLengthInputStream(in, 0, true, connection, doClose);
+ }
+ else if ("chunked".equalsIgnoreCase(transferCoding))
+ {
+ in = new LimitedLengthInputStream(in, -1, false, connection, doClose);
+
+ in = new ChunkedInputStream(in, responseHeaders);
+ }
+ else
+ {
+ contentLength = responseHeaders.getLongValue("Content-Length");
+
+ if (contentLength < 0)
+ doClose = true; // No Content-Length, must close.
+
+ in = new LimitedLengthInputStream(in, contentLength,
+ contentLength >= 0,
+ connection, doClose);
+ }
+ String contentCoding = responseHeaders.getValue("Content-Encoding");
+ if (contentCoding != null && !"identity".equals(contentCoding))
+ {
+ if ("gzip".equals(contentCoding))
+ {
+ in = new GZIPInputStream(in);
+ }
+ else if ("deflate".equals(contentCoding))
+ {
+ in = new InflaterInputStream(in);
+ }
+ else
+ {
+ throw new ProtocolException("Unsupported Content-Encoding: " +
+ contentCoding);
+ }
+ // Remove the Content-Encoding header because the content is
+ // no longer compressed.
+ responseHeaders.remove("Content-Encoding");
+ }
+ return in;
+ }
+
+ boolean authenticate(Response response, int attempts)
+ throws IOException
+ {
+ String challenge = response.getHeader("WWW-Authenticate");
+ if (challenge == null)
+ {
+ challenge = response.getHeader("Proxy-Authenticate");
+ }
+ int si = challenge.indexOf(' ');
+ String scheme = (si == -1) ? challenge : challenge.substring(0, si);
+ if ("Basic".equalsIgnoreCase(scheme))
+ {
+ Properties params = parseAuthParams(challenge.substring(si + 1));
+ String realm = params.getProperty("realm");
+ Credentials creds = authenticator.getCredentials(realm, attempts);
+ String userPass = creds.getUsername() + ':' + creds.getPassword();
+ byte[] b_userPass = userPass.getBytes("US-ASCII");
+ byte[] b_encoded = Base64.encode(b_userPass).getBytes("US-ASCII");
+ String authorization =
+ scheme + " " + new String(b_encoded, "US-ASCII");
+ setHeader("Authorization", authorization);
+ return true;
+ }
+ else if ("Digest".equalsIgnoreCase(scheme))
+ {
+ Properties params = parseAuthParams(challenge.substring(si + 1));
+ String realm = params.getProperty("realm");
+ String nonce = params.getProperty("nonce");
+ String qop = params.getProperty("qop");
+ String algorithm = params.getProperty("algorithm");
+ String digestUri = getRequestURI();
+ Credentials creds = authenticator.getCredentials(realm, attempts);
+ String username = creds.getUsername();
+ String password = creds.getPassword();
+ connection.incrementNonce(nonce);
+ try
+ {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ final byte[] COLON = { 0x3a };
+
+ // Calculate H(A1)
+ md5.reset();
+ md5.update(username.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(realm.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(password.getBytes("US-ASCII"));
+ byte[] ha1 = md5.digest();
+ if ("md5-sess".equals(algorithm))
+ {
+ byte[] cnonce = generateNonce();
+ md5.reset();
+ md5.update(ha1);
+ md5.update(COLON);
+ md5.update(nonce.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(cnonce);
+ ha1 = md5.digest();
+ }
+ String ha1Hex = toHexString(ha1);
+
+ // Calculate H(A2)
+ md5.reset();
+ md5.update(method.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(digestUri.getBytes("US-ASCII"));
+ if ("auth-int".equals(qop))
+ {
+ byte[] hEntity = null; // TODO hash of entity body
+ md5.update(COLON);
+ md5.update(hEntity);
+ }
+ byte[] ha2 = md5.digest();
+ String ha2Hex = toHexString(ha2);
+
+ // Calculate response
+ md5.reset();
+ md5.update(ha1Hex.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(nonce.getBytes("US-ASCII"));
+ if ("auth".equals(qop) || "auth-int".equals(qop))
+ {
+ String nc = getNonceCount(nonce);
+ byte[] cnonce = generateNonce();
+ md5.update(COLON);
+ md5.update(nc.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(cnonce);
+ md5.update(COLON);
+ md5.update(qop.getBytes("US-ASCII"));
+ }
+ md5.update(COLON);
+ md5.update(ha2Hex.getBytes("US-ASCII"));
+ String digestResponse = toHexString(md5.digest());
+
+ String authorization = scheme +
+ " username=\"" + username + "\"" +
+ " realm=\"" + realm + "\"" +
+ " nonce=\"" + nonce + "\"" +
+ " uri=\"" + digestUri + "\"" +
+ " response=\"" + digestResponse + "\"";
+ setHeader("Authorization", authorization);
+ return true;
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ return false;
+ }
+ }
+ // Scheme not recognised
+ return false;
+ }
+
+ Properties parseAuthParams(String text)
+ {
+ int len = text.length();
+ String key = null;
+ CPStringBuilder buf = new CPStringBuilder();
+ Properties ret = new Properties();
+ boolean inQuote = false;
+ for (int i = 0; i < len; i++)
+ {
+ char c = text.charAt(i);
+ if (c == '"')
+ {
+ inQuote = !inQuote;
+ }
+ else if (c == '=' && key == null)
+ {
+ key = buf.toString().trim();
+ buf.setLength(0);
+ }
+ else if (c == ' ' && !inQuote)
+ {
+ String value = unquote(buf.toString().trim());
+ ret.put(key, value);
+ key = null;
+ buf.setLength(0);
+ }
+ else if (c != ',' || (i <(len - 1) && text.charAt(i + 1) != ' '))
+ {
+ buf.append(c);
+ }
+ }
+ if (key != null)
+ {
+ String value = unquote(buf.toString().trim());
+ ret.put(key, value);
+ }
+ return ret;
+ }
+
+ String unquote(String text)
+ {
+ int len = text.length();
+ if (len > 0 && text.charAt(0) == '"' && text.charAt(len - 1) == '"')
+ {
+ return text.substring(1, len - 1);
+ }
+ return text;
+ }
+
+ /**
+ * Returns the number of times the specified nonce value has been seen.
+ * This always returns an 8-byte 0-padded hexadecimal string.
+ */
+ String getNonceCount(String nonce)
+ {
+ int nc = connection.getNonceCount(nonce);
+ String hex = Integer.toHexString(nc);
+ CPStringBuilder buf = new CPStringBuilder();
+ for (int i = 8 - hex.length(); i > 0; i--)
+ {
+ buf.append('0');
+ }
+ buf.append(hex);
+ return buf.toString();
+ }
+
+ /**
+ * Client nonce value.
+ */
+ byte[] nonce;
+
+ /**
+ * Generates a new client nonce value.
+ */
+ byte[] generateNonce()
+ throws IOException, NoSuchAlgorithmException
+ {
+ if (nonce == null)
+ {
+ long time = System.currentTimeMillis();
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(Long.toString(time).getBytes("US-ASCII"));
+ nonce = md5.digest();
+ }
+ return nonce;
+ }
+
+ String toHexString(byte[] bytes)
+ {
+ char[] ret = new char[bytes.length * 2];
+ for (int i = 0, j = 0; i < bytes.length; i++)
+ {
+ int c =(int) bytes[i];
+ if (c < 0)
+ {
+ c += 0x100;
+ }
+ ret[j++] = Character.forDigit(c / 0x10, 0x10);
+ ret[j++] = Character.forDigit(c % 0x10, 0x10);
+ }
+ return new String(ret);
+ }
+
+ /**
+ * Parse the specified cookie list and notify the cookie manager.
+ */
+ void handleSetCookie(String text)
+ {
+ CookieManager cookieManager = connection.getCookieManager();
+ if (cookieManager == null)
+ {
+ return;
+ }
+ String name = null;
+ String value = null;
+ String comment = null;
+ String domain = connection.getHostName();
+ String path = this.path;
+ int lsi = path.lastIndexOf('/');
+ if (lsi != -1)
+ {
+ path = path.substring(0, lsi);
+ }
+ boolean secure = false;
+ Date expires = null;
+
+ int len = text.length();
+ String attr = null;
+ CPStringBuilder buf = new CPStringBuilder();
+ boolean inQuote = false;
+ for (int i = 0; i <= len; i++)
+ {
+ char c =(i == len) ? '\u0000' : text.charAt(i);
+ if (c == '"')
+ {
+ inQuote = !inQuote;
+ }
+ else if (!inQuote)
+ {
+ if (c == '=' && attr == null)
+ {
+ attr = buf.toString().trim();
+ buf.setLength(0);
+ }
+ else if (c == ';' || i == len || c == ',')
+ {
+ String val = unquote(buf.toString().trim());
+ if (name == null)
+ {
+ name = attr;
+ value = val;
+ }
+ else if ("Comment".equalsIgnoreCase(attr))
+ {
+ comment = val;
+ }
+ else if ("Domain".equalsIgnoreCase(attr))
+ {
+ domain = val;
+ }
+ else if ("Path".equalsIgnoreCase(attr))
+ {
+ path = val;
+ }
+ else if ("Secure".equalsIgnoreCase(val))
+ {
+ secure = true;
+ }
+ else if ("Max-Age".equalsIgnoreCase(attr))
+ {
+ int delta = Integer.parseInt(val);
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(System.currentTimeMillis());
+ cal.add(Calendar.SECOND, delta);
+ expires = cal.getTime();
+ }
+ else if ("Expires".equalsIgnoreCase(attr))
+ {
+ DateFormat dateFormat = new HTTPDateFormat();
+ try
+ {
+ expires = dateFormat.parse(val);
+ }
+ catch (ParseException e)
+ {
+ // if this isn't a valid date, it may be that
+ // the value was returned unquoted; in that case, we
+ // want to continue buffering the value
+ buf.append(c);
+ continue;
+ }
+ }
+ attr = null;
+ buf.setLength(0);
+ // case EOL
+ if (i == len || c == ',')
+ {
+ Cookie cookie = new Cookie(name, value, comment, domain,
+ path, secure, expires);
+ cookieManager.setCookie(cookie);
+ }
+ if (c == ',')
+ {
+ // Reset cookie fields
+ name = null;
+ value = null;
+ comment = null;
+ domain = connection.getHostName();
+ path = this.path;
+ if (lsi != -1)
+ {
+ path = path.substring(0, lsi);
+ }
+ secure = false;
+ expires = null;
+ }
+ }
+ else
+ {
+ buf.append(c);
+ }
+ }
+ else
+ {
+ buf.append(c);
+ }
+ }
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/RequestBodyWriter.java b/libjava/classpath/gnu/java/net/protocol/http/RequestBodyWriter.java
new file mode 100644
index 000000000..aa5b78a54
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/RequestBodyWriter.java
@@ -0,0 +1,68 @@
+/* RequestBodyWriter.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+/**
+ * Callback interface for writing request body content.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public interface RequestBodyWriter
+{
+
+ /**
+ * Returns the total number of bytes that will be written in a single pass
+ * by this writer.
+ */
+ int getContentLength();
+
+ /**
+ * Initialises the writer.
+ * This will be called before each pass.
+ */
+ void reset();
+
+ /**
+ * Writes body content to the supplied buffer.
+ * @param buffer the content buffer
+ * @return the number of bytes written
+ */
+ int write(byte[] buffer);
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/Response.java b/libjava/classpath/gnu/java/net/protocol/http/Response.java
new file mode 100644
index 000000000..084cf75fa
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/Response.java
@@ -0,0 +1,223 @@
+/* Response.java --
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import java.io.InputStream;
+import java.util.Date;
+
+/**
+ * An HTTP response.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class Response
+{
+
+ /**
+ * The HTTP major version of the server issuing the response.
+ */
+ protected final int majorVersion;
+
+ /**
+ * The HTTP minor version of the server issuing the response.
+ */
+ protected final int minorVersion;
+
+ /**
+ * The HTTP status code of the response.
+ */
+ protected final int code;
+
+ /**
+ * Human-readable text of the response.
+ */
+ protected final String message;
+
+ /**
+ * The response headers.
+ */
+ protected final Headers headers;
+
+ /**
+ * An InputStream that returns the body of the response.
+ */
+ protected final InputStream body;
+
+ /**
+ * Constructs a new response with the specified parameters.
+ */
+ protected Response(int majorVersion, int minorVersion, int code,
+ String message, Headers headers, InputStream body)
+ {
+ this.majorVersion = majorVersion;
+ this.minorVersion = minorVersion;
+ this.code = code;
+ this.message = message;
+ this.headers = headers;
+ this.body = body;
+ }
+
+ /**
+ * Returns the HTTP major version of the server issuing the response.
+ * @see #majorVersion
+ */
+ public int getMajorVersion()
+ {
+ return majorVersion;
+ }
+
+ /**
+ * Returns the HTTP minor version of the server issuing the response.
+ * @see #minorVersion
+ */
+ public int getMinorVersion()
+ {
+ return minorVersion;
+ }
+
+ /**
+ * Returns the HTTP status code of the response.
+ * @see #code
+ */
+ public int getCode()
+ {
+ return code;
+ }
+
+ /**
+ * Returns the class of the response. This is the most significant
+ * digit of the status code.
+ *
+ * 1xx
- Informational response
+ * 2xx
- Success
+ * 3xx
- Redirection
+ * 4xx
- Client error
+ * 5xx
- Server error
+ *
+ */
+ public int getCodeClass()
+ {
+ return code / 100;
+ }
+
+ /**
+ * Returns the human-readable text of the response.
+ * @see #message
+ */
+ public String getMessage()
+ {
+ return message;
+ }
+
+ /**
+ * Returns the headers in the response.
+ */
+ public Headers getHeaders()
+ {
+ return headers;
+ }
+
+ /**
+ * Returns the header value for the specified name.
+ * @param name the header name
+ */
+ public String getHeader(String name)
+ {
+ return headers.getValue(name);
+ }
+
+ /**
+ * Returns the header value for the specified name as an integer.
+ * @param name the header name
+ */
+ public int getIntHeader(String name)
+ {
+ return headers.getIntValue(name);
+ }
+
+ /**
+ * Returns the header value for the specified name as a long.
+ * @param name the header name
+ */
+ public long getLongHeader(String name)
+ {
+ return headers.getLongValue(name);
+ }
+
+ /**
+ * Returns the header value for the specified name as a date.
+ * @param name the header name
+ */
+ public Date getDateHeader(String name)
+ {
+ return headers.getDateValue(name);
+ }
+
+ /**
+ * Tests whether this response indicates a redirection.
+ *
+ * @return true
if, false
otherwise.
+ */
+ public boolean isRedirect()
+ {
+ return (code != 304 && getCodeClass() == 3);
+ }
+
+ /**
+ * Tests whether this response indicates an error.
+ * Errors are the response codes 4xx
- Client error and
+ * 5xx
- Server error.
+ *
+ * @return true
if, false
otherwise.
+ */
+ public boolean isError()
+ {
+ return (getCodeClass() == 4 || getCodeClass() == 5);
+ }
+
+ /**
+ * Returns an InputStream that returns the body of the response.
+ *
+ * @return the body of the response
+ */
+ public InputStream getBody()
+ {
+ return body;
+ }
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/ResponseHeaderHandler.java b/libjava/classpath/gnu/java/net/protocol/http/ResponseHeaderHandler.java
new file mode 100644
index 000000000..ca863440e
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/ResponseHeaderHandler.java
@@ -0,0 +1,56 @@
+/* ResponseHeaderHandler.java --
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+/**
+ * Callback interface for objects that wish to be notified of response
+ * header values.
+ * @see Request#setResponseHeaderHandler(String, ResponseHeaderHandler)
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public interface ResponseHeaderHandler
+{
+
+ /**
+ * Sets the value for the header associated with this handler.
+ */
+ void setValue(String value);
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/SimpleCookieManager.java b/libjava/classpath/gnu/java/net/protocol/http/SimpleCookieManager.java
new file mode 100644
index 000000000..f08204769
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/SimpleCookieManager.java
@@ -0,0 +1,137 @@
+/* CookieManager.java --
+ Copyright (C) 2004, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A simple non-persistent cookie manager. This class can be extended to
+ * provide cookie persistence.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class SimpleCookieManager
+ implements CookieManager
+{
+
+ /**
+ * The cookie cache.
+ * This is a dictionary mapping domains to maps of cookies by name.
+ */
+ protected Map> cookies;
+
+ /**
+ * Constructor.
+ */
+ public SimpleCookieManager()
+ {
+ cookies = new HashMap>();
+ }
+
+ public void setCookie(Cookie cookie)
+ {
+ String domain = cookie.getDomain();
+ Map map = cookies.get(domain);
+ if (map == null)
+ {
+ map = new HashMap();
+ cookies.put(domain, map);
+ }
+ String name = cookie.getName();
+ map.put(name, cookie); // will replace a cookie of the same name
+ }
+
+ public Cookie[] getCookies(String host, boolean secure, String path)
+ {
+ ArrayList matches = new ArrayList();
+ Date now = new Date();
+ if (Character.isLetter(host.charAt(0)))
+ {
+ int di = host.indexOf('.');
+ while (di != -1)
+ {
+ addCookies(matches, host, secure, path, now);
+ host = host.substring(di);
+ di = host.indexOf('.', 1);
+ }
+ }
+ addCookies(matches, host, secure, path, now);
+ Cookie[] ret = new Cookie[matches.size()];
+ matches.toArray(ret);
+ return ret;
+ }
+
+ private void addCookies(ArrayList matches, String domain,
+ boolean secure, String path, Date now)
+ {
+ Map map = cookies.get(domain);
+ if (map != null)
+ {
+ ArrayList expired = new ArrayList();
+ for (Map.Entry entry : map.entrySet())
+ {
+ Cookie cookie = entry.getValue();
+ Date expires = cookie.getExpiryDate();
+ if (expires != null && expires.before(now))
+ {
+ expired.add(entry.getKey());
+ continue;
+ }
+ if (secure && !cookie.isSecure())
+ {
+ continue;
+ }
+ if (path.startsWith(cookie.getPath()))
+ {
+ matches.add(cookie);
+ }
+ }
+ // Good housekeeping
+ for (Iterator i = expired.iterator(); i.hasNext(); )
+ {
+ map.remove(i.next());
+ }
+ }
+ }
+
+}
diff --git a/libjava/classpath/gnu/java/net/protocol/http/package.html b/libjava/classpath/gnu/java/net/protocol/http/package.html
new file mode 100644
index 000000000..8cf7c1e16
--- /dev/null
+++ b/libjava/classpath/gnu/java/net/protocol/http/package.html
@@ -0,0 +1,76 @@
+
+
+
+
+GNU Classpath - gnu.java.net.protocol.http
+
+
+
+
+This package contains an HTTP/1.1 client, as described in RFC 2616.
+It supports the following features:
+
+- Persistent connections
+- Basic and Digest authentication (RFC 2617)
+- HTTPS
+- HTTP proxies
+- HTTP/1.0 compatibility
+- Support for WebDAV methods and other HTTP extensions
+- Automatic decoding of the chunked transfer-coding
+- Parsing of HTTP date headers
+- Support for the 100-continue expectation
+
+
+
+
+The API is similar to the neon
+WebDAV/HTTP library. A logical connection to the server is instantiated,
+and multiple requests can be issued for this connection. Each request
+has an atomic dispatch
method which returns the response.
+All I/O, authentication, etc is handled by registering callback objects
+with the request prior to dispatch, which are notified during the dispatch
+procedure as necessary. Simple byte-array content callbacks are supplied
+which can manage any request/response content that fits in available memory.
+
+
+
+An URL stream handler is provided, supporting the full HttpURLConnection
+specification.
+
+
+
--
cgit v1.2.3