You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by br...@apache.org on 2008/03/13 19:28:50 UTC
svn commit: r636822 [2/9] - in /maven/archiva/branches/springy: ./
archiva-base/archiva-common/
archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/
archiva-base/archiva-consumers/archiva-...
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/README-it.could-webdav.txt
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/README-it.could-webdav.txt?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/README-it.could-webdav.txt (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/README-it.could-webdav.txt Thu Mar 13 11:28:26 2008
@@ -0,0 +1,8 @@
+This library contains the patched sources to the it.could simple WebDAV library r280, licensed under the Apache License 2.0.
+
+http://could.it/main/a-simple-approach-to-webdav.html
+
+To later return to a released version (after the patches have been incorporated and released):
+- remove src/main/java/it and src/main/java/org/betaversion
+- remove <build> <resources> from the POM
+- replace the servlet-api dependency in the POM with it.could webdav.
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/README-it.could-webdav.txt
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/pom.xml
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/pom.xml?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/pom.xml (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/pom.xml Thu Mar 13 11:28:26 2008
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.maven.archiva</groupId>
+ <artifactId>archiva-web</artifactId>
+ <version>1.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>archiva-webdav</artifactId>
+ <name>Archiva WebDAV Provider</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-spring</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>2.5.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+<!-- We import these classes directly to be able to patch them, since this library hasn't been released in some time
+ <dependency>
+ <groupId>it.could</groupId>
+ <artifactId>webdav</artifactId>
+ <version>0.4</version>
+ </dependency>
+-->
+
+ <!-- Required by it.could classes -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>slide</groupId>
+ <artifactId>slide-webdavlib</artifactId>
+ <version>2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty</artifactId>
+ <version>6.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <!-- Required by it.could classes -->
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ <resource>
+ <directory>${project.build.sourceDirectory}</directory>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ <exclude>**/package.html</exclude>
+ <exclude>**/url.gif</exclude>
+ <exclude>**/url.pdf</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ </build>
+</project>
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/pom.xml
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java Thu Mar 13 11:28:26 2008
@@ -0,0 +1,125 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * <p>An utility class providing various static methods operating on
+ * {@link InputStream input} and {@link OutputStream output} streams.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public final class StreamTools {
+
+ /** <p>Deny construction.</p> */
+ private StreamTools() { };
+
+ /**
+ * <p>Copy every byte from the specified {@link InputStream} to the specifed
+ * {@link OutputStream} and then close both of them.</p>
+ *
+ * <p>This method is equivalent to a call to the following method:
+ * {@link #copy(InputStream,OutputStream,boolean) copy(in, out, true)}.</p>
+ *
+ * @param in the {@link InputStream} to read bytes from.
+ * @param out the {@link OutputStream} to write bytes to.
+ * @return the number of bytes copied.
+ * @throws IOException if an I/O error occurred copying the data.
+ */
+ public static long copy(InputStream in, OutputStream out)
+ throws IOException {
+ return copy(in, out, true);
+ }
+
+ /**
+ * <p>Copy every byte from the specified {@link InputStream} to the specifed
+ * {@link OutputStream} and then optionally close both of them.</p>
+ *
+ * @param in the {@link InputStream} to read bytes from.
+ * @param out the {@link OutputStream} to write bytes to.
+ * @param close whether to close the streams or not.
+ * @return the number of bytes copied.
+ * @throws IOException if an I/O error occurred copying the data.
+ */
+ public static long copy(InputStream in, OutputStream out, boolean close)
+ throws IOException {
+ if (in == null) throw new NullPointerException("Null input");
+ if (out == null) throw new NullPointerException("Null output");
+
+ final byte buffer[] = new byte[4096];
+ int length = -1;
+ long total = 0;
+ while ((length = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, length);
+ total += length;
+ }
+
+ if (close) {
+ in.close();
+ out.close();
+ }
+
+ return total;
+ }
+
+ /**
+ * Closes the output stream. The output stream can be null and any IOException's will be swallowed.
+ *
+ * @param outputStream The stream to close.
+ */
+ public static void close( OutputStream outputStream )
+ {
+ if ( outputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ outputStream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the input stream. The input stream can be null and any IOException's will be swallowed.
+ *
+ * @param inputStream The stream to close.
+ */
+ public static void close( InputStream inputStream )
+ {
+ if ( inputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ inputStream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore
+ }
+ }
+}
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java Thu Mar 13 11:28:26 2008
@@ -0,0 +1,214 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util;
+
+import it.could.util.encoding.Encodable;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <p>An utility class providing various static methods operating on
+ * {@link String}s.</p>
+ *
+ * <p>This class implement the {@link Encodable} interface from which it
+ * inherits its {@link Encodable#DEFAULT_ENCODING default encoding}.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public final class StringTools {
+
+ /** <p>The {@link SimpleDateFormat} RFC-822 date format.</p> */
+ private static final String FORMAT_822 = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
+ /** <p>The {@link SimpleDateFormat} RFC-822 date format.</p> */
+ private static final String FORMAT_ISO = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+ /** <p>The {@link TimeZone} to use for dates.</p> */
+ private static final TimeZone TIMEZONE = TimeZone.getTimeZone("GMT");
+ /** <p>The {@link Locale} to use for dates.</p> */
+ private static final Locale LOCALE = Locale.US;
+
+ /** <p>Deny construction.</p> */
+ private StringTools() { }
+
+ /* ====================================================================== */
+ /* NUMBER AND DATE PARSING AND FORMATTING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Format a {@link Number} into a {@link String} making sure that
+ * {@link NullPointerException}s are not thrown.</p>
+ *
+ * @param number the {@link Number} to format.
+ * @return a {@link String} instance or <b>null</b> if the object was null.
+ */
+ public static String formatNumber(Number number) {
+ if (number == null) return null;
+ return (number.toString());
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Long}.</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Long} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Long parseNumber(String string) {
+ if (string == null) return null;
+ try {
+ return new Long(string);
+ } catch (NumberFormatException exception) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Format a {@link Date} according to the HTTP/1.1 RFC.</p>
+ *
+ * @param date the {@link Date} to format.
+ * @return a {@link String} instance or <b>null</b> if the date was null.
+ */
+ public static String formatHttpDate(Date date) {
+ if (date == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_822, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ return formatter.format(date);
+ }
+
+ /**
+ * <p>Format a {@link Date} according to the ISO 8601 specification.</p>
+ *
+ * @param date the {@link Date} to format.
+ * @return a {@link String} instance or <b>null</b> if the date was null.
+ */
+ public static String formatIsoDate(Date date) {
+ if (date == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_ISO, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ return formatter.format(date);
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Date} according to the
+ * HTTP/1.1 RFC (<code>Mon, 31 Jan 2000 11:59:00 GMT</code>).</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Date} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Date parseHttpDate(String string) {
+ if (string == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_822, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ try {
+ return formatter.parse(string);
+ } catch (ParseException exception) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Date} according to the ISO 8601
+ * specification (<code>2000-12-31T11:59:00Z</code>).</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Date} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Date parseIsoDate(String string) {
+ if (string == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_ISO, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ try {
+ return formatter.parse(string);
+ } catch (ParseException exception) {
+ return null;
+ }
+ }
+
+ /* ====================================================================== */
+ /* STRING SPLITTING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Split the specified string in two parts according to the specified
+ * delimiter, and any resulting path of zero length will be converted to
+ * <b>null</b>.</p>
+ */
+ public static String[] splitOnce(String source, char delimiter,
+ boolean noDelimReturnSecond) {
+ if (source == null) return new String[] { null, null };
+ final int position = source.indexOf(delimiter);
+ if (position < 0) { // --> first
+ if (noDelimReturnSecond) return new String[] { null, source };
+ else return new String[] { source, null };
+ } else if (position == 0) {
+ if (source.length() == 1) { // --> |
+ return new String[] { null, null };
+ } else { // --> |second
+ return new String[] { null, source.substring(1) };
+ }
+ } else {
+ final String first = source.substring(0, position);
+ if (source.length() -1 == position) { // --> first|
+ return new String[] { first, null };
+ } else { // --> first|second
+ return new String[] { first, source.substring(position + 1) };
+ }
+ }
+ }
+
+ /**
+ * <p>Split the specified string according to the specified delimiter, and
+ * any resulting path of zero length will be converted to <b>null</b>.</p>
+ */
+ public static String[] splitAll(String source, char delimiter) {
+ final List strings = new ArrayList();
+ String current = source;
+ while (current != null) {
+ String split[] = splitOnce(current, delimiter, false);
+ strings.add(split[0]);
+ current = split[1];
+ }
+ if (current != null) strings.add(current);
+ final int length = source.length();
+ if ((length > 0) && (source.charAt(length - 1) == delimiter)) {
+ strings.add(null);
+ }
+ return (String []) strings.toArray(new String[strings.size()]);
+ }
+
+ /**
+ * <p>Find the first occurrence of one of the specified delimiter characters
+ * in the specified source string.</p>
+ */
+ public static int findFirst(String source, String delimiters) {
+ final char array[] = source.toCharArray();
+ final char delim[] = delimiters.toCharArray();
+ for (int x = 0; x < array.length; x ++) {
+ for (int y = 0; y < delim.length; y ++) {
+ if (array[x] == delim[y]) return x;
+ }
+ }
+ return -1;
+ }
+}
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java Thu Mar 13 11:28:26 2008
@@ -0,0 +1,48 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.encoding;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * <p>The {@link Encodable} interface describes an {@link Object} whose
+ * {@link String} representation can vary depending on the encoding used.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public interface Encodable extends EncodingAware {
+
+ /**
+ * <p>Return the {@link String} representation of this instance.</p>
+ *
+ * <p>This method is equivalent to a call to
+ * {@link #toString(String) toString}({@link EncodingAware#DEFAULT_ENCODING
+ * DEFAULT_ENCODING})</p>
+ */
+ public String toString();
+
+ /**
+ * <p>Return the {@link String} representation of this instance given
+ * a specific character encoding.</p>
+ *
+ * @throws UnsupportedEncodingException if the specified encoding is not
+ * supported by the platform.
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException;
+
+}
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java Thu Mar 13 11:28:26 2008
@@ -0,0 +1,37 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.encoding;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+
+/**
+ * <p>The {@link EncodingAware} interface describes an {@link Object} aware
+ * of multiple encodings existing withing the platform.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public interface EncodingAware {
+
+ /** <p>The default encoding is specified as being <code>UTF-8</code>.</p> */
+ public static final String DEFAULT_ENCODING = "UTF-8";
+
+ /** <p>The platform encoding is evaluated at runtime from the JVM.</p> */
+ public static final String PLATFORM_ENCODING =
+ new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding();
+
+}
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java Thu Mar 13 11:28:26 2008
@@ -0,0 +1,274 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.encoding;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+/**
+ * <p>An utility class providing various static methods dealing with
+ * encodings and {@link Encodable} objects..</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public final class EncodingTools implements EncodingAware {
+
+ /** <p>The Base-64 alphabet.</p> */
+ private static final char ALPHABET[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' };
+
+ /** <p>Deny construction of this class.</p> */
+ private EncodingTools() { }
+
+ /* ====================================================================== */
+ /* URL ENCODING / DECODING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the {@link String} representation of the specified
+ * {@link Encodable} object using the {@link EncodingAware#DEFAULT_ENCODING
+ * default encoding}.</p>
+ *
+ * throws NullPointerException if the {@link Encodable} was <b>null</b>.
+ */
+ public static String toString(Encodable encodable) {
+ try {
+ return encodable.toString(DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Default encoding \"" + DEFAULT_ENCODING +
+ "\" not supported by the platform";
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+ /* URL ENCODING / DECODING */
+ /* ====================================================================== */
+
+ /**
+ * <p>URL-encode the specified string.</p>
+ */
+ public static String urlEncode(String source, String encoding)
+ throws UnsupportedEncodingException {
+ if (source == null) return null;
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ return URLEncoder.encode(source, encoding);
+ }
+
+ /**
+ * <p>URL-encode the specified string.</p>
+ */
+ public static String urlEncode(String source) {
+ if (source == null) return null;
+ try {
+ return URLEncoder.encode(source, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>URL-decode the specified string.</p>
+ */
+ public static String urlDecode(String source, String encoding)
+ throws UnsupportedEncodingException {
+ if (source == null) return null;
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ return URLDecoder.decode(source, encoding);
+ }
+
+ /**
+ * <p>URL-decode the specified string.</p>
+ */
+ public static String urlDecode(String source) {
+ if (source == null) return null;
+ try {
+ return URLDecoder.decode(source, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+ /* BASE 64 ENCODING / DECODING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Encode the specified string in base 64 using the specified
+ * encoding.</p>
+ */
+ public static final String base64Encode(String string, String encoding)
+ throws UnsupportedEncodingException {
+ /* Check the source string for null or the empty string. */
+ if (string == null) return (null);
+ if (string.length() == 0) return "";
+
+ /* Check the encoding */
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+
+ /* Prepare the buffers that we'll use to encode in Base 64 */
+ final byte bsrc[] = string.getBytes(encoding);
+ final char bdst[] = new char[(bsrc.length + 2) / 3 * 4];
+
+ /* Iterate into the source in chunks of three bytes */
+ int psrc = -1;
+ int pdst = 0;
+ int temp = 0;
+ while ((psrc = psrc + 3) < bsrc.length) {
+ /* For every three bytes processed ... */
+ temp = ((bsrc[psrc - 2] << 16) & 0xFF0000) |
+ ((bsrc[psrc - 1] << 8) & 0x00FF00) |
+ ((bsrc[psrc ] ) & 0x0000FF);
+ /* ... we append four bytes to the buffer */
+ bdst[pdst ++] = ALPHABET[(temp >> 18) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp >> 12) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp >> 6) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp ) & 0x3f];
+ }
+
+ /* Let's check whether we still have some bytes to encode */
+ switch (psrc - bsrc.length) {
+ case 0: /* Two bytes left to encode */
+ temp = ((bsrc[psrc - 2] & 0xFF) << 8) | (bsrc[psrc - 1] & 0xFF);
+ bdst[pdst ++] = ALPHABET[(temp >> 10) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp >> 4) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp << 2) & 0x3c];
+ bdst[pdst ++] = ALPHABET[64];
+ break;
+ case 1: /* One byte left to encode */
+ temp = (bsrc[psrc - 2] & 0xFF);
+ bdst[pdst ++] = ALPHABET[(temp >> 2) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp << 4) & 0x30];
+ bdst[pdst ++] = ALPHABET[64];
+ bdst[pdst ++] = ALPHABET[64];
+ }
+
+ /* Convert the character array into a proper string */
+ return new String(bdst);
+ }
+
+ /**
+ * <p>Encode the specified string in base 64 using the default encoding.</p>
+ */
+ public static final String base64Encode(String string) {
+ try {
+ return (base64Encode(string, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Decode the specified base 64 string using the specified encoding.</p>
+ */
+ public static final String base64Decode(String string, String encoding)
+ throws UnsupportedEncodingException {
+ /* Check the source string for null or the empty string. */
+ if (string == null) return (null);
+ if (string.length() == 0) return "";
+
+ /* Check the encoding */
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+
+ /* Retrieve the array of characters of the source string. */
+ final char characters[] = string.toCharArray();
+
+ /* Check the length, which must be dividible by 4. */
+ if ((characters.length & 0x03) != 0)
+ throw new IllegalArgumentException("Invalid length for the "+
+ "encoded string (" + characters.length + ")");
+
+ /* The bytes array length is 3/4th of the characters array length */
+ byte bytes[] = new byte[characters.length - (characters.length >> 2)];
+
+ /*
+ * Since this might take a while check now for the last 4 characters
+ * token: it must contain at most two == and those need to be in the
+ * last two positions in the array (the only valid sequences are:
+ * "????", "???=" and "??==").
+ */
+ if (((characters[characters.length - 4] == '=') ||
+ (characters[characters.length - 3] == '=')) ||
+ ((characters[characters.length - 2] == '=') &&
+ (characters[characters.length - 1] != '='))) {
+ throw new IllegalArgumentException("Invalid pattern for last " +
+ "Base64 token in string to decode: " +
+ characters[characters.length - 4] +
+ characters[characters.length - 3] +
+ characters[characters.length - 2] +
+ characters[characters.length - 1]);
+ }
+
+ /* Translate the Base64-encoded String in chunks of 4 characters. */
+ int coff = 0;
+ int boff = 0;
+ while (coff < characters.length) {
+ boolean last = (coff == (characters.length - 4));
+ int curr = ((value(characters[coff ], last) << 0x12) |
+ (value(characters[coff + 1], last) << 0x0c) |
+ (value(characters[coff + 2], last) << 0x06) |
+ (value(characters[coff + 3], last) ));
+ bytes[boff + 2] = (byte)((curr ) & 0xff);
+ bytes[boff + 1] = (byte)((curr >> 0x08) & 0xff);
+ bytes[boff ] = (byte)((curr >> 0x10) & 0xff);
+ coff += 4;
+ boff += 3;
+ }
+
+ /* Get the real decoded string length, checking out the trailing '=' */
+ if (characters[coff - 1] == '=') boff--;
+ if (characters[coff - 2] == '=') boff--;
+
+ /* All done */
+ return (new String(bytes, 0, boff, encoding));
+ }
+
+ /**
+ * <p>Decode the specified base 64 string using the default encoding.</p>
+ */
+ public static final String base64Decode(String string) {
+ try {
+ return (base64Decode(string, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+
+ /** <p>Retrieve the offset of a character in the base 64 alphabet.</p> */
+ private static final int value(char character, boolean last) {
+ for (int x = 0; x < 64; x++) if (ALPHABET[x] == character) return (x);
+ if (last && (character == ALPHABET[65])) return(0);
+ final String message = "Character \"" + character + "\" invalid";
+ throw new IllegalArgumentException(message);
+ }
+}
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html Thu Mar 13 11:28:26 2008
@@ -0,0 +1,61 @@
+<html>
+ <head>
+ <title>Encoding Utilities</title>
+ </head>
+ <body>
+ <p>
+ This package contains a number of utility classes dealing with generic
+ encoding of {@link java.lang.String}s.
+ </p>
+ <p>
+ Although this might sound useless at first (as {@link java.lang.String}s
+ do support encoding internally already), this class deals with a very
+ subtle problem encountered when merging Java {@link java.lang.String}s
+ and old byte-based (non internationalized) transports, such as
+ Base 64 and URL encoding.
+ </p>
+ <p>
+ Let's consider (as an example) the URL encoded {@link java.lang.String}
+ <code>%C2%A3 100</code> can be easily decomposed in a byte array using
+ URL decoding techniques: we would end up with the following byte array:
+ <code>0x0C2 0x0A3 0x20 0x31 0x30 0x30</code>.
+ </p>
+ <p>
+ This byte-array, though, doesn't tell us anything about how to represent
+ this as a readable and usable {@link java.lang.String} in Java. To be
+ able to convert this we have to decode it again using a charset (or an
+ encoding).
+ </p>
+ <p>
+ So, for example, if we were to decode the above mentioned byte array using
+ the <b>ISO-8859-1</b> encoding, we would obtain the string
+ "<code>£ 100</code>", or in details:
+ </p>
+ <ul>
+ <li>a latin capital letter "A" with a circumflex accent</li>
+ <li>the pound sign</li>
+ <li>a space</li>
+ <li>the number 1</li>
+ <li>the number 0</li>
+ <li>the number 0</li>
+ </ul>
+ <p>
+ If we were to decode the same byte sequence using <b>UTF-8</b>, on the
+ other hand, we would obtain the (quite different) string
+ "<code>£ 100</code>", or in details:
+ </p>
+ <ul>
+ <li>the pound sign</li>
+ <li>a space</li>
+ <li>the number 1</li>
+ <li>the number 0</li>
+ <li>the number 0</li>
+ </ul>
+ <p>
+ Therefore, as a conclusion, when Java {@link java.lang.String}s are
+ encoded using Base 64, URL encoding, or similar techiques, one always
+ have to remember that encoding (or decoding) must be done twice, and
+ this package provides a way to deal with this mechanism.
+ </p>
+ </body>
+</html>
\ No newline at end of file
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html
------------------------------------------------------------------------------
svn:eol-style = native
Added: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java
URL: http://svn.apache.org/viewvc/maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java?rev=636822&view=auto
==============================================================================
--- maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java (added)
+++ maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java Thu Mar 13 11:28:26 2008
@@ -0,0 +1,1070 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.http;
+
+import it.could.util.encoding.EncodingTools;
+import it.could.util.location.Location;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>A class implementing an extremely simple HTTP 1.0 connector with
+ * basic authentication support.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class HttpClient {
+
+ /** <p>The default HTTP method to use.</p> */
+ public static final String DEFAULT_METHOD = "GET";
+
+ /* ====================================================================== */
+
+ /** <p>The byte sequence CR LF (the end of the request).</p> */
+ private static final byte CRLF[] = { 0x0d, 0x0a };
+ /** <p>The byte sequence for " HTTP/1.0\r\n" (the request signature).</p> */
+ private static final byte HTTP[] = { 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f,
+ 0x31, 0x2e, 0x30, 0x0d, 0x0a };
+
+ /* ====================================================================== */
+
+ /** <p>The buffer used to parse lines in the response.</p> */
+ private final byte buffer[] = new byte[4096];
+ /** <p>The map of the current request headers.</p> */
+ private final Map requestHeaders = new HashMap();
+ /** <p>The map of the current response headers.</p> */
+ private final Map responseHeaders = new HashMap();
+
+ /* ====================================================================== */
+
+ /** <p>The {@link Location} pointing to the current request.</p> */
+ private Location location;
+ /** <p>The status of the current request.</p> */
+ private Status status = null;
+ /** <p>An array of acceptable statuses to verify upon connection.</p> */
+ private int acceptable[] = null;
+
+ /* ====================================================================== */
+
+ /** <p>The limited input stream associated with this request.</p> */
+ private Input xinput = null;
+ /** <p>The limited output stream associated with this request.</p> */
+ private Output xoutput = null;
+ /** <p>The socket associated with this request.</p> */
+ private Socket xsocket = null;
+
+ /* ====================================================================== */
+
+ /**
+ * <p>Create a new {@link HttpClient} instance associated with the
+ * specified location in string format.</p>
+ *
+ * @throws MalformedURLException if the location couldn't be parsed.
+ */
+ public HttpClient(String location)
+ throws MalformedURLException {
+ this.location = Location.parse(location);
+ }
+
+ /**
+ * <p>Create a new {@link HttpClient} instance associated with the
+ * specified location in string format.</p>
+ *
+ * @throws MalformedURLException if the location couldn't be parsed.
+ */
+ public HttpClient(String location, String encoding)
+ throws MalformedURLException, UnsupportedEncodingException {
+ this.location = Location.parse(location, encoding);
+ }
+
+ /**
+ * <p>Create a new {@link HttpClient} instance associated with the
+ * specified {@link Location}.</p>
+ */
+ public HttpClient(Location location) {
+ if (location == null) throw new NullPointerException("Null location");
+ if (! location.isAbsolute())
+ throw new IllegalArgumentException("Relative location supplied");
+ if (! "http".equals(location.getSchemes().toString())) {
+ throw new IllegalArgumentException("Scheme is not HTTP");
+ }
+ this.location = location;
+ }
+
+ /* ====================================================================== */
+ /* CONNECTION VERIFICATION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Set an HTTP response status code considered to be acceptable when
+ * verifying the connection.</p>
+ */
+ public HttpClient setAcceptableStatus(int status) {
+ return this.setAcceptableStatuses(new int[] { status });
+ }
+
+ /**
+ * <p>Set an array of HTTP response status codes considered to be acceptable
+ * when verifying the connection.</p>
+ *
+ * <p>If the array is <b>null</b> status code checking is disabled.</p>
+ */
+ public HttpClient setAcceptableStatuses(int statuses[]) {
+ if (statuses == null) {
+ this.acceptable = null;
+ return this;
+ }
+ for (int x = 0; x < statuses.length; x ++) {
+ final int status = statuses[x];
+ if ((status < 100) || (status > 599))
+ throw new IllegalArgumentException("Wrong status " + status);
+ }
+ this.acceptable = statuses;
+ return this;
+ }
+
+ /* ====================================================================== */
+ /* CONNECTION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction using the
+ * default method <code>GET</code>.</p>
+ *
+ * <p>This is equivalent to {@link #connect(boolean) connect(true)}.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect()
+ throws IOException {
+ return this.connect(DEFAULT_METHOD, true, 0);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction using the
+ * default method <code>GET</code> allowing for a specified amount of
+ * content to be written into the request.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(long contentLength)
+ throws IOException {
+ return this.connect(DEFAULT_METHOD, false, contentLength);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction using the
+ * default method <code>GET</code> and optionally following redirects.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(boolean followRedirects)
+ throws IOException {
+ return this.connect(DEFAULT_METHOD, followRedirects, 0);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction with the
+ * specified method.</p>
+ *
+ * <p>This is equivalent to {@link #connect(String,boolean)
+ * connect(method, true)}.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(String method)
+ throws IOException {
+ return this.connect(method, true, 0);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction with the
+ * specified method allowing for a specified amount of content to be
+ * written into the request.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(String method, long contentLength)
+ throws IOException {
+ return this.connect(method, false, contentLength);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction with the
+ * specified method and optionally following redirects.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(String method, boolean followRedirects)
+ throws IOException {
+ return this.connect(method, followRedirects, 0);
+ }
+
+ /**
+ * <p>Disconnect from the remote endpoint and terminate the request.</p>
+ *
+ * <p>Note that request and response headers, the resultin status and
+ * acceptable statuses are <b>not</b> cleared by this method.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient disconnect()
+ throws IOException {
+ return this.disconnect(false);
+ }
+
+ /**
+ * <p>Disconnect from the remote endpoint and terminate the request.</p>
+ *
+ * @param reset whether to reset all headers, status and acceptable response
+ * status codes or not.
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient disconnect(boolean reset)
+ throws IOException {
+ final Socket socket = this.xsocket;
+ if (socket != null) try {
+ /* Make sure that we mark this instance as being closed */
+ this.xsocket = null;
+
+ /* Close the input stream if necessary */
+ if (this.xinput != null) {
+ if (! this.xinput.closed) this.xinput.close();
+ this.xinput = null;
+ }
+
+ /* Close the output stream if necessary */
+ if (this.xoutput != null) {
+ if (! this.xoutput.closed) this.xoutput.close();
+ this.xoutput = null;
+ }
+
+ } finally {
+ /* Ensure that the socket is closed */
+ socket.close();
+ }
+
+ if (reset) {
+ this.requestHeaders.clear();
+ this.responseHeaders.clear();
+ this.status = null;
+ this.acceptable = null;
+ }
+ return this;
+ }
+
+ /* ====================================================================== */
+ /* INTERNAL CONNECTION HANDLER */
+ /* ====================================================================== */
+
+ /**
+ * <p>Internal method actually connecting to the remote HTTP server.</p>
+ */
+ private HttpClient connect(String method, boolean redirect, long length)
+ throws IOException {
+ /* Check if (by any chance) we have been connected already */
+ if (this.xsocket != null)
+ throw new IllegalStateException("Already connected");
+
+ /* Check for both follow redirects and content length */
+ if (length < 0) throw new IOException("Negative length");
+ if ((length > 0) && redirect)
+ throw new InternalError("Can't follow redirects and write request");
+
+ /* Verify any authentication token */
+ final String userinfo = this.location.getAuthority().getUserInfo();
+ if (userinfo != null) {
+ final String encoded = EncodingTools.base64Encode(userinfo);
+ this.addRequestHeader("Authorization", "Basic " + encoded);
+ }
+
+ /* All methods in HTTP are upper case */
+ method = method.toUpperCase();
+
+ /* Make sure we close the connection at the end of the request */
+ this.addRequestHeader("Connection", "close", false);
+
+ /* The content length of the request is forced to be valid */
+ this.addRequestHeader("Content-Length", Long.toString(length), false);
+
+ /* Enter in a loop for redirections */
+ int redirs = 20;
+ while (true) {
+ /* If we have been redirected too many times, fail */
+ if ((--redirs) < 0) throw new IOException("Too many redirections");
+
+ /* Get the authority, once and for all */
+ final Location.Authority auth = this.location.getAuthority();
+
+ /* Prepare a normalized host header */
+ final String host = auth.getHost();
+ final int port = auth.getPort() < 0 ? 80 : auth.getPort();
+ this.addRequestHeader("Host", host + ":" + port, false);
+
+ /* Connect to the remote endpoint */
+ final Socket sock = new Socket(auth.getHost(), port);
+ final InputStream in = sock.getInputStream();
+ final OutputStream out = sock.getOutputStream();
+
+ /* Write the request line */
+ out.write((method + " ").getBytes("US-ASCII"));
+ out.write(this.location.getPath().toString().getBytes("US-ASCII"));
+ out.write(HTTP); /* SPACE HTTP/1.0 CR LF */
+
+ /* Write all the headers */
+ final Iterator headers = this.requestHeaders.values().iterator();
+ while (headers.hasNext()) {
+ final RequestHeader header = (RequestHeader) headers.next();
+ final Iterator values = header.values.iterator();
+ while (values.hasNext()) {
+ out.write(header.name);
+ out.write((byte []) values.next());
+ }
+ }
+
+ /* Write the final CRLF, read the status and the headers */
+ out.write(CRLF);
+ out.flush();
+
+ /* Return now if we have to write content */
+ if (length > 0) {
+ this.xsocket = sock;
+ this.xoutput = new Output(this, in, out, length);
+ this.xinput = null;
+ return this;
+ }
+
+ this.readStatusLine(in);
+ this.readHeaders(in);
+
+ /* If we have to follow redirects, let's inspect the response */
+ final int code = this.status.status;
+ if (redirect && ((code == 301) || (code == 302) || (code == 307))) {
+ final String location = this.getResponseHeader("Location");
+ if (location != null) {
+ in.close();
+ out.close();
+ sock.close();
+ this.location = this.location.resolve(location);
+ continue;
+ }
+ }
+
+ /* No further redirections, so verify if the status code is ok */
+ this.verify();
+
+ /* Evaluate the content length specified by the server */
+ final String len = this.getResponseHeader("Content-Length");
+ long bytesLength = -1;
+ if (len != null) try {
+ bytesLength = Long.parseLong(len);
+ } catch (NumberFormatException exception) {
+ /* Swallow this, be liberal in what we accept */
+ }
+
+ /* Return an output stream if the content length was not zero */
+ this.xsocket = sock;
+ this.xoutput = null;
+ this.xinput = new Input(this, in, bytesLength);
+ return this;
+ }
+ }
+
+ private void verify()
+ throws IOException {
+ /* No further redirections, sov erify if the status code is ok */
+ if (this.acceptable != null) {
+ boolean accepted = false;
+ for (int x = 0; x < this.acceptable.length; x ++) {
+ if (this.status.status != this.acceptable[x]) continue;
+ accepted = true;
+ break;
+ }
+ if (! accepted) {
+ this.disconnect();
+ throw new IOException("Connection to " + this.location +
+ " returned unacceptable status " +
+ this.status.status + " (" +
+ this.status.message + ")");
+ }
+ }
+ }
+
+ /* ====================================================================== */
+ /* INPUT / OUTPUT METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return an {@link InputStream} where the content of the HTTP response
+ * can be read from.</p>
+ *
+ * @throws IllegalStateException if this instance is not connected yet, or
+ * the request body was not fully written yet.
+ */
+ public InputStream getResponseStream()
+ throws IllegalStateException {
+ if (this.xsocket == null)
+ throw new IllegalStateException("Connection not available");
+ if ((this.xoutput != null) && (this.xoutput.remaining != 0))
+ throw new IllegalStateException("Request body not fully written");
+ return this.xinput;
+ }
+
+ /**
+ * <p>Return an {@link OutputStream} where the content of the HTTP request
+ * can be written to.</p>
+ *
+ * @throws IllegalStateException if this instance is not connected yet or if
+ * upon connection the size of the request was
+ * not specifed or <b>zero</b>.
+ */
+ public OutputStream getRequestStream()
+ throws IllegalStateException {
+ if (this.xsocket == null)
+ throw new IllegalStateException("Connection not available");
+ if (this.xoutput == null)
+ throw new IllegalStateException("No request body to write to");
+ return this.xoutput;
+ }
+
+ /* ====================================================================== */
+ /* REQUEST AND RESPONSE METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the {@link Location} of this connection.</p>
+ *
+ * <p>This might be different from the {@link Location} specified at
+ * construction time if upon connecting HTTP redirections were followed.</p>
+ */
+ public Location getLocation() {
+ return this.location;
+ }
+
+ /**
+ * <p>Add a new header that will be sent with the HTTP request.</p>
+ *
+ * <p>This method will remove any header value previously associated with
+ * the specified name, in other words this method is equivalent to
+ * {@link #addRequestHeader(String, String, boolean)
+ * addRequestHeader(name, value, false)}.</p>
+ *
+ * @param name the name of the request header to add.
+ * @param value the value of the request header to add.
+ * @return this {@link HttpClient} instance.
+ * @throws NullPointerException the name or value were <b>null</b>.
+ */
+ public HttpClient addRequestHeader(String name, String value) {
+ return this.addRequestHeader(name, value, false);
+ }
+
+ /**
+ * <p>Add a new header that will be sent with the HTTP request.</p>
+ *
+ * @param name the name of the request header to add.
+ * @param value the value of the request header to add.
+ * @param appendValue if the current value should be appended, or in other
+ * words, that two headers with the same can coexist.
+ * @return this {@link HttpClient} instance.
+ * @throws NullPointerException the name or value were <b>null</b>.
+ */
+ public HttpClient addRequestHeader(String name, String value,
+ boolean appendValue) {
+ final String key = name.toLowerCase();
+ try {
+ RequestHeader header;
+ if (appendValue) {
+ header = (RequestHeader) this.requestHeaders.get(key);
+ if (header == null) {
+ header = new RequestHeader(name);
+ this.requestHeaders.put(key, header);
+ }
+ } else {
+ header = new RequestHeader(name);
+ this.requestHeaders.put(key, header);
+ }
+ header.values.add((value + "\r\n").getBytes("ISO-8859-1"));
+ return this;
+ } catch (UnsupportedEncodingException exception) {
+ Error error = new InternalError("Standard encoding not supported");
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Remove the named header from the current HTTP request.</p>
+ *
+ * @param name the name of the request header to add.
+ * @return this {@link HttpClient} instance.
+ * @throws NullPointerException the name was <b>null</b>.
+ */
+ public HttpClient removeRequestHeader(String name) {
+ final String key = name.toLowerCase();
+ this.requestHeaders.remove(key);
+ return this;
+ }
+
+ /**
+ * <p>Remove all headers from the current HTTP request.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ */
+ public HttpClient removeRequestHeaders() {
+ this.requestHeaders.clear();
+ return this;
+ }
+
+ /**
+ * <p>Return the first value for the specified response header.</p>
+ *
+ * @param name the name of the header whose value needs to be returned.
+ * @return a {@link String} or <b>null</b> if no such header exists.
+ */
+ public String getResponseHeader(String name) {
+ final String key = name.toLowerCase();
+ ResponseHeader header = (ResponseHeader) this.responseHeaders.get(key);
+ if (header == null) return null;
+ return (String) header.values.get(0);
+ }
+
+ /**
+ * <p>Return all the values for the specified response header.</p>
+ *
+ * @param name the name of the header whose values needs to be returned.
+ * @return a {@link List} or <b>null</b> if no such header exists.
+ */
+ public List getResponseHeaderValues(String name) {
+ final String key = name.toLowerCase();
+ ResponseHeader header = (ResponseHeader) this.responseHeaders.get(key);
+ if (header == null) return null;
+ return Collections.unmodifiableList(header.values);
+ }
+
+ /**
+ * <p>Return an {@link Iterator} over all response header names.</p>
+ *
+ * @return a <b>non-null</b> {@link Iterator}.
+ */
+ public Iterator getResponseHeaderNames() {
+ final Iterator iterator = this.responseHeaders.values().iterator();
+ return new Iterator() {
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+ public Object next() {
+ return ((ResponseHeader) iterator.next()).name;
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * <p>Return the protocol returned by the remote HTTP server.</p>
+ *
+ * @return a <b>non-null</b> {@link String} like <code>HTTP/1.0</code>.
+ * @throws IllegalStateException if the connection was never connected.
+ */
+ public String getResponseProtocol() {
+ if (this.status == null) throw new IllegalStateException();
+ return this.status.protocol;
+ }
+
+ /**
+ * <p>Return the status returned by the remote HTTP server.</p>
+ *
+ * @return a number representing the HTTP status of the response.
+ * @throws IllegalStateException if the connection was never connected.
+ */
+ public int getResponseStatus() {
+ if (this.status == null) throw new IllegalStateException();
+ return this.status.status;
+ }
+
+ /**
+ * <p>Return the status message returned by the remote HTTP server.</p>
+ *
+ * @return a <b>non-null</b> {@link String} like <code>OK</code>.
+ * @throws IllegalStateException if the connection was never connected.
+ */
+ public String getResponseMessage() {
+ if (this.status == null) throw new IllegalStateException();
+ return this.status.message;
+ }
+
+ /* ====================================================================== */
+ /* PRIVATE METHODS TO USE WHEN CONNECTING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Read a single line of the HTTP response from the specified
+ * {@link InputStream} into a byte array (trailing CRLF are removed).</p>
+ */
+ private byte[] readLine(InputStream input)
+ throws IOException {
+ int x = 0;
+ while (true) {
+ int b = input.read();
+ if (b == -1) break;
+ if (b == 0x0A) break;
+ if (x == this.buffer.length) break;
+ this.buffer[x ++] = (byte) b;
+ }
+ if ((x > 0) && (this.buffer[x - 1] == 0x0D)) x--;
+ final byte array[] = new byte[x];
+ System.arraycopy(this.buffer, 0, array, 0, x);
+ return array;
+ }
+
+ /**
+ * <p>Read the status line from the specified {@link InputStream} and
+ * setup the {@link #status} field.</p>
+ */
+ private void readStatusLine(InputStream input)
+ throws IOException {
+ /* Prepare the different buffers required for parsing */
+ final byte line[] = this.readLine(input);
+ final byte buff[] = new byte[line.length];
+ final String comp[] = new String[3];
+ int lpos = 0;
+ int bpos = 0;
+ int cpos = 0;
+ boolean spc = true;
+
+ /* Iterate every single byte in the line, splitting up components */
+ while (lpos < line.length) {
+ final byte b = line[lpos ++];
+ if (spc) {
+ if ((b == 0x09) || (b == 0x20)) continue;
+ buff[bpos ++] = b;
+ if (cpos == 2) break;
+ else spc = false;
+ } else {
+ if ((b == 0x09) || (b == 0x20)) {
+ comp[cpos ++] = new String(buff, 0, bpos, "US-ASCII");
+ bpos = 0;
+ spc = true;
+ continue;
+ }
+ buff[bpos ++] = b;
+ }
+
+ }
+ /*
+ * Copy remaining bytes out of the line buffer and ensure all
+ * components in the status line are not null;
+ */
+ while (lpos < line.length) buff[bpos ++] = line[lpos++];
+ if (bpos > 0) comp[cpos++] = new String(buff, 0, bpos, "US-ASCII");
+ for (int x = cpos; x < 3; x++) comp[x] = "";
+
+ /* Create the status object */
+ this.status = new Status(comp[0], comp[1], comp[2]);
+ }
+
+ /**
+ * <p>Read all the response headers from the specified {@link InputStream}
+ * and setup the {@link #responseHeaders} field.</p>
+ */
+ private void readHeaders(InputStream input)
+ throws IOException {
+ /* Clear out any previous header */
+ this.responseHeaders.clear();
+
+ /* Process the input stream until we find an empty line */
+ while (true) {
+ final byte array[] = this.readLine(input);
+ if (array.length == 0) break;
+
+ /* Identify where the colon is in the header */
+ int pos = -1;
+ while (pos < array.length) if (array[++ pos] == 0x03A) break;
+ if (pos == 0) continue;
+ if (pos == array.length - 1) continue;
+
+ /* Prepare strings for name and value */
+ final int o = pos + 1;
+ final int l = array.length - o;
+ final String name = new String(array, 0, pos, "US-ASCII").trim();
+ final String value = new String(array, o, l, "ISO-8859-1").trim();
+ if ((name.length() == 0) || (value.length() == 0)) continue;
+
+ /* Store the header value in a list for now */
+ final String key = name.toLowerCase();
+ ResponseHeader hdr = (ResponseHeader) this.responseHeaders.get(key);
+ if (hdr == null) {
+ hdr = new ResponseHeader(name);
+ this.responseHeaders.put(key, hdr);
+ }
+ hdr.values.add(value);
+ }
+ }
+
+ /* ====================================================================== */
+ /* INTERNAL CLASS REPRESENTNG THE STATUS LINE AND AN ENCODED HEADER */
+ /* ====================================================================== */
+
+ /**
+ * <p>A simple internal class representing a response status line.</p>
+ */
+ private static final class Status {
+
+ /** <p>The response protocol, like <code>HTTP/1.0</code> */
+ private final String protocol;
+ /** <p>The response status code, like <code>302</code> */
+ private final int status;
+ /** <p>The response message, like <code>Moved permanently</code> */
+ private final String message;
+
+ /**
+ * <p>Create a new {@link Status} verifying the supplied parameters.</p>
+ *
+ * @throws IOException if an error occurred verifying the parameters.
+ */
+ private Status(String protocol, String status, String message)
+ throws IOException {
+
+ /* Verify the protocol */
+ if ("HTTP/1.0".equals(protocol) || "HTTP/1.1".equals(protocol)) {
+ this.protocol = protocol;
+ } else {
+ throw new IOException("Unknown protocol \"" + protocol + "\"");
+ }
+
+ /* Verify the status */
+ try {
+ this.status = Integer.parseInt(status);
+ if ((this.status < 100) || (this.status > 599)) {
+ throw new IOException("Invalid status \"" + status + "\"");
+ }
+ } catch (RuntimeException exception) {
+ final String error = "Can't parse status \"" + status + "\"";
+ IOException throwable = new IOException(error);
+ throw (IOException) throwable.initCause(exception);
+ }
+
+ /* Decode the message */
+ if ("".equals(message)) this.message = "No message";
+ else this.message = EncodingTools.urlDecode(message, "ISO-8859-1");
+ }
+ }
+
+ /**
+ * <p>A simple internal class representing a request header.</p>
+ */
+ private static final class RequestHeader {
+
+ /** <p>The byte array of the header's name.</p> */
+ private final byte name[];
+ /** <p>A {@link List} of all the header's values.</p> */
+ private final List values;
+
+ /** <p>Create a new {@link RequestHeader} instance.</p> */
+ private RequestHeader(String name)
+ throws UnsupportedEncodingException {
+ this.name = (name + ": ").getBytes("US-ASCII");
+ this.values = new ArrayList();
+ }
+ }
+
+ /**
+ * <p>A simple internal class representing a response header.</p>
+ */
+ private static final class ResponseHeader {
+
+ /** <p>The real name of the response header.</p> */
+ private final String name;
+ /** <p>A {@link List} of all the header's values.</p> */
+ private final List values;
+
+ /** <p>Create a new {@link ResponseHeader} instance.</p> */
+ private ResponseHeader(String name)
+ throws UnsupportedEncodingException {
+ this.name = name;
+ this.values = new ArrayList();
+ }
+ }
+
+ /* ====================================================================== */
+ /* LIMITED STREAMS */
+ /* ====================================================================== */
+
+ /**
+ * <p>A simple {@link OutputStream} writing at most the number of bytes
+ * specified at construction.</p>
+ */
+ private static final class Output extends OutputStream {
+
+ /** <p>The {@link OutputStream} wrapped by this instance.</p> */
+ private final OutputStream output;
+ /** <p>The {@link InputStream} wrapped by this instance.</p> */
+ private final InputStream input;
+ /** <p>The {@link HttpClient} wrapped by this instance.</p> */
+ private final HttpClient client;
+ /** <p>The number of bytes yet to write.</p> */
+ private long remaining;
+ /** <p>A flag indicating whether this instance was closed.</p> */
+ private boolean closed;
+
+ /**
+ * <p>Create a new {@link Output} instance with the specified limit
+ * of bytes to write.</p>
+ *
+ * @param output the {@link OutputStream} to wrap.
+ * @param remainig the maximum number of bytes to write.
+ */
+ private Output(HttpClient client, InputStream input,
+ OutputStream output, long remaining) {
+ if (input == null) throw new NullPointerException();
+ if (output == null) throw new NullPointerException();
+ if (client == null) throw new NullPointerException();
+ this.remaining = remaining;
+ this.client = client;
+ this.output = output;
+ this.input = input;
+ }
+
+ public void write(byte buf[])
+ throws IOException {
+ this.write(buf, 0, buf.length);
+ }
+
+ public void write(byte buf[], int off, int len)
+ throws IOException {
+ if (len > this.remaining) {
+ throw new IOException("Too much data to write");
+ } else try {
+ this.output.write(buf, off, len);
+ } finally {
+ this.remaining -= len;
+ if (this.remaining < 1) this.close();
+ }
+ }
+
+ public void write(int b)
+ throws IOException {
+ if (this.remaining < 1) {
+ throw new IOException("Too much data to write");
+ } else try {
+ this.output.write(b);
+ } finally {
+ this.remaining -= 1;
+ if (this.remaining < 1) this.close();
+ }
+ }
+
+ public void flush()
+ throws IOException {
+ this.output.flush();
+ }
+
+ public void close()
+ throws IOException {
+ if (this.closed) return;
+ if (this.remaining > 0)
+ throw new IOException(this.remaining + " bytes left to write");
+ this.closed = true;
+ this.output.flush();
+
+ /* Read the status and headers from the connection and verify */
+ this.client.readStatusLine(this.input);
+ this.client.readHeaders(this.input);
+ this.client.verify();
+
+ /* Evaluate the content length specified by the server */
+ final String slen = this.client.getResponseHeader("Content-Length");
+ long blen = -1;
+ if (slen != null) try {
+ blen = Long.parseLong(slen);
+ } catch (NumberFormatException exception) {
+ /* Swallow this, be liberal in what we accept */
+ }
+
+ /* Return an output stream if the content length was not zero */
+ this.client.xoutput = null;
+ this.client.xinput = new Input(this.client, this.input, blen);
+ }
+
+ protected void finalize()
+ throws Throwable {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ /**
+ * <p>A simple {@link InputStream} reading at most the number of bytes
+ * specified at construction.</p>
+ */
+ private static final class Input extends InputStream {
+
+ /** <p>The {@link InputStream} wrapped by this instance.</p> */
+ private final InputStream input;
+ /** <p>The {@link HttpClient} wrapped by this instance.</p> */
+ private final HttpClient client;
+ /** <p>The number of bytes yet to write or -1 if unknown.</p> */
+ private long remaining;
+ /** <p>A flag indicating whether this instance was closed.</p> */
+ private boolean closed;
+
+ /**
+ * <p>Create a new {@link Input} instance with the specified limit
+ * of bytes to read.</p>
+ *
+ * @param input the {@link InputStream} to wrap.
+ * @param remainig the maximum number of bytes to read or -1 if unknown.
+ */
+ private Input(HttpClient client, InputStream input, long remaining) {
+ if (input == null) throw new NullPointerException();
+ if (client == null) throw new NullPointerException();
+ this.remaining = remaining < 0 ? Long.MAX_VALUE : remaining;
+ this.client = client;
+ this.input = input;
+ }
+
+ public int read()
+ throws IOException {
+ if (this.remaining < 1) {
+ return -1;
+ } else try {
+ return this.input.read();
+ } finally {
+ this.remaining -= 1;
+ if (this.remaining < 1) this.close();
+ }
+ }
+
+ public int read(byte buf[])
+ throws IOException {
+ return read(buf, 0, buf.length);
+ }
+
+ public int read(byte buf[], int off, int len)
+ throws IOException {
+ if (this.remaining <= 0) return -1;
+ if (len > this.remaining) len = (int) this.remaining;
+ int count = 0;
+ try {
+ count = this.input.read(buf, off, len);
+ } finally {
+ this.remaining -= count;
+ if (this.remaining < 1) this.close();
+ }
+ return count;
+ }
+
+ public long skip(long n)
+ throws IOException {
+ if (this.remaining <= 0) return -1;
+ if (n > this.remaining) n = this.remaining;
+ long count = 0;
+ try {
+ count = this.input.skip(n);
+ } finally {
+ this.remaining -= count;
+ if (this.remaining < 1) this.close();
+ }
+ return count;
+ }
+
+ public int available()
+ throws IOException {
+ int count = this.input.available();
+ if (count < this.remaining) return count;
+ return (int) this.remaining;
+ }
+
+ public void close()
+ throws IOException {
+ if (this.closed) return;
+ this.closed = true;
+ try {
+ this.input.close();
+ } finally {
+ this.client.disconnect();
+ }
+ }
+
+ public void mark(int readlimit) {
+ this.input.mark(readlimit);
+ }
+
+ public void reset()
+ throws IOException {
+ this.input.reset();
+ }
+
+ public boolean markSupported() {
+ return this.input.markSupported();
+ }
+
+ protected void finalize()
+ throws Throwable {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ /* ====================================================================== */
+ /* UTILITY FETCHER */
+ /* ====================================================================== */
+
+ /**
+ * <p><b>Utility method:</b> fetch the location specified on the command
+ * line following redirects if necessary.</p>
+ *
+ * <p>The final location fetched (in case of redirections it might change)
+ * will be reported on the {@link System#err system error stream} alongside
+ * with any errors encountered while processing.</p>
+ */
+ public static final void main(String args[]) {
+ try {
+ final HttpClient c = new HttpClient(args[0]).connect();
+ final InputStream i = c.getResponseStream();
+ for (int b = i.read(); b >= 0; b = i.read()) System.out.write(b);
+ c.disconnect();
+ } catch (Throwable throwable) {
+ throwable.printStackTrace(System.err);
+ }
+ }
+}
Propchange: maven/archiva/branches/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java
------------------------------------------------------------------------------
svn:eol-style = native