diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:16 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:16 -0800 |
commit | 069490a5ca2fd1988d29daf45d892f47ad665115 (patch) | |
tree | aea04c65769a1d9e3ca6fde36a7d23bd91dbeb98 /src | |
parent | e5d9544310b857f3ee9ec172bdbff8077323f9a1 (diff) | |
download | external_apache-http-069490a5ca2fd1988d29daf45d892f47ad665115.zip external_apache-http-069490a5ca2fd1988d29daf45d892f47ad665115.tar.gz external_apache-http-069490a5ca2fd1988d29daf45d892f47ad665115.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'src')
431 files changed, 60872 insertions, 0 deletions
diff --git a/src/org/apache/commons/codec/BinaryDecoder.java b/src/org/apache/commons/codec/BinaryDecoder.java new file mode 100644 index 0000000..7aebabf --- /dev/null +++ b/src/org/apache/commons/codec/BinaryDecoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Defines common decoding methods for byte array decoders. + * + * @author Apache Software Foundation + * @version $Id: BinaryDecoder.java,v 1.10 2004/06/15 18:14:15 ggregory Exp $ + */ +public interface BinaryDecoder extends Decoder { + + /** + * Decodes a byte array and returns the results as a byte array. + * + * @param pArray A byte array which has been encoded with the + * appropriate encoder + * + * @return a byte array that contains decoded content + * + * @throws DecoderException A decoder exception is thrown + * if a Decoder encounters a failure condition during + * the decode process. + */ + byte[] decode(byte[] pArray) throws DecoderException; +} + diff --git a/src/org/apache/commons/codec/BinaryEncoder.java b/src/org/apache/commons/codec/BinaryEncoder.java new file mode 100644 index 0000000..52859ed --- /dev/null +++ b/src/org/apache/commons/codec/BinaryEncoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Defines common encoding methods for byte array encoders. + * + * @author Apache Software Foundation + * @version $Id: BinaryEncoder.java,v 1.10 2004/02/29 04:08:31 tobrien Exp $ + */ +public interface BinaryEncoder extends Encoder { + + /** + * Encodes a byte array and return the encoded data + * as a byte array. + * + * @param pArray Data to be encoded + * + * @return A byte array containing the encoded data + * + * @throws EncoderException thrown if the Encoder + * encounters a failure condition during the + * encoding process. + */ + byte[] encode(byte[] pArray) throws EncoderException; +} + diff --git a/src/org/apache/commons/codec/Decoder.java b/src/org/apache/commons/codec/Decoder.java new file mode 100644 index 0000000..184920c --- /dev/null +++ b/src/org/apache/commons/codec/Decoder.java @@ -0,0 +1,54 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * <p>Provides the highest level of abstraction for Decoders. + * This is the sister interface of {@link Encoder}. All + * Decoders implement this common generic interface.</p> + * + * <p>Allows a user to pass a generic Object to any Decoder + * implementation in the codec package.</p> + * + * <p>One of the two interfaces at the center of the codec package.</p> + * + * @author Apache Software Foundation + * @version $Id: Decoder.java,v 1.9 2004/02/29 04:08:31 tobrien Exp $ + */ +public interface Decoder { + + /** + * Decodes an "encoded" Object and returns a "decoded" + * Object. Note that the implementation of this + * interface will try to cast the Object parameter + * to the specific type expected by a particular Decoder + * implementation. If a {@link java.lang.ClassCastException} occurs + * this decode method will throw a DecoderException. + * + * @param pObject an object to "decode" + * + * @return a 'decoded" object + * + * @throws DecoderException a decoder exception can + * be thrown for any number of reasons. Some good + * candidates are that the parameter passed to this + * method is null, a param cannot be cast to the + * appropriate type for a specific encoder. + */ + Object decode(Object pObject) throws DecoderException; +} + diff --git a/src/org/apache/commons/codec/DecoderException.java b/src/org/apache/commons/codec/DecoderException.java new file mode 100644 index 0000000..f35c016 --- /dev/null +++ b/src/org/apache/commons/codec/DecoderException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Thrown when a Decoder has encountered a failure condition during a decode. + * + * @author Apache Software Foundation + * @version $Id: DecoderException.java,v 1.9 2004/02/29 04:08:31 tobrien Exp $ + */ +public class DecoderException extends Exception { + + /** + * Creates a DecoderException + * + * @param pMessage A message with meaning to a human + */ + public DecoderException(String pMessage) { + super(pMessage); + } + +} + diff --git a/src/org/apache/commons/codec/Encoder.java b/src/org/apache/commons/codec/Encoder.java new file mode 100644 index 0000000..fa339ee --- /dev/null +++ b/src/org/apache/commons/codec/Encoder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * <p>Provides the highest level of abstraction for Encoders. + * This is the sister interface of {@link Decoder}. Every implementation of + * Encoder provides this common generic interface whic allows a user to pass a + * generic Object to any Encoder implementation in the codec package.</p> + * + * @author Apache Software Foundation + * @version $Id: Encoder.java,v 1.10 2004/02/29 04:08:31 tobrien Exp $ + */ +public interface Encoder { + + /** + * Encodes an "Object" and returns the encoded content + * as an Object. The Objects here may just be <code>byte[]</code> + * or <code>String</code>s depending on the implementation used. + * + * @param pObject An object ot encode + * + * @return An "encoded" Object + * + * @throws EncoderException an encoder exception is + * thrown if the encoder experiences a failure + * condition during the encoding process. + */ + Object encode(Object pObject) throws EncoderException; +} + diff --git a/src/org/apache/commons/codec/EncoderException.java b/src/org/apache/commons/codec/EncoderException.java new file mode 100644 index 0000000..0e202c1 --- /dev/null +++ b/src/org/apache/commons/codec/EncoderException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Thrown when there is a failure condition during the encoding process. This + * exception is thrown when an Encoder encounters a encoding specific exception + * such as invalid data, inability to calculate a checksum, characters outside of the + * expected range. + * + * @author Apache Software Foundation + * @version $Id: EncoderException.java,v 1.10 2004/02/29 04:08:31 tobrien Exp $ + */ +public class EncoderException extends Exception { + + /** + * Creates a new instance of this exception with an useful message. + * + * @param pMessage a useful message relating to the encoder specific error. + */ + public EncoderException(String pMessage) { + super(pMessage); + } +} + diff --git a/src/org/apache/commons/codec/StringDecoder.java b/src/org/apache/commons/codec/StringDecoder.java new file mode 100644 index 0000000..9b1a0cd --- /dev/null +++ b/src/org/apache/commons/codec/StringDecoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Decodes a String into a String. + * + * @author Apache Software Foundation + * @version $Id: StringDecoder.java,v 1.9 2004/02/29 04:08:31 tobrien Exp $ + */ +public interface StringDecoder extends Decoder { + + /** + * Decodes a String and returns a String. + * + * @param pString a String to encode + * + * @return the encoded String + * + * @throws DecoderException thrown if there is + * an error conidition during the Encoding process. + */ + String decode(String pString) throws DecoderException; +} + diff --git a/src/org/apache/commons/codec/StringEncoder.java b/src/org/apache/commons/codec/StringEncoder.java new file mode 100644 index 0000000..46f5404 --- /dev/null +++ b/src/org/apache/commons/codec/StringEncoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +/** + * Encodes a String into a String. + * + * @author Apache Software Foundation + * @version $Id: StringEncoder.java,v 1.9 2004/02/29 04:08:31 tobrien Exp $ + */ +public interface StringEncoder extends Encoder { + + /** + * Encodes a String and returns a String. + * + * @param pString a String to encode + * + * @return the encoded String + * + * @throws EncoderException thrown if there is + * an error conidition during the Encoding process. + */ + String encode(String pString) throws EncoderException; +} + diff --git a/src/org/apache/commons/codec/StringEncoderComparator.java b/src/org/apache/commons/codec/StringEncoderComparator.java new file mode 100644 index 0000000..6d29af2 --- /dev/null +++ b/src/org/apache/commons/codec/StringEncoderComparator.java @@ -0,0 +1,83 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec; + +import java.util.Comparator; + +/** + * Strings are comparable, and this comparator allows + * you to configure it with an instance of a class + * which implements StringEncoder. This comparator + * is used to sort Strings by an encoding scheme such + * as Soundex, Metaphone, etc. This class can come in + * handy if one need to sort Strings by an encoded + * form of a name such as Soundex. + * + * @author Apache Software Foundation + * @version $Id: StringEncoderComparator.java,v 1.14 2004/06/21 23:24:17 ggregory Exp $ + */ +public class StringEncoderComparator implements Comparator { + + /** + * Internal encoder instance. + */ + private StringEncoder stringEncoder; + + /** + * Constructs a new instance. + */ + public StringEncoderComparator() { + // no init. + } + + /** + * Constructs a new instance with the given algorithm. + * @param stringEncoder the StringEncoder used for comparisons. + */ + public StringEncoderComparator(StringEncoder stringEncoder) { + this.stringEncoder = stringEncoder; + } + + /** + * Compares two strings based not on the strings + * themselves, but on an encoding of the two + * strings using the StringEncoder this Comparator + * was created with. + * + * If an {@link EncoderException} is encountered, return <code>0</code>. + * + * @param o1 the object to compare + * @param o2 the object to compare to + * @return the Comparable.compareTo() return code or 0 if an encoding error was caught. + * @see Comparable + */ + public int compare(Object o1, Object o2) { + + int compareCode = 0; + + try { + Comparable s1 = (Comparable) ((Encoder) this.stringEncoder).encode(o1); + Comparable s2 = (Comparable) ((Encoder) this.stringEncoder).encode(o2); + compareCode = s1.compareTo(s2); + } + catch (EncoderException ee) { + compareCode = 0; + } + return compareCode; + } + +} diff --git a/src/org/apache/commons/codec/binary/Base64.java b/src/org/apache/commons/codec/binary/Base64.java new file mode 100644 index 0000000..ea479e9 --- /dev/null +++ b/src/org/apache/commons/codec/binary/Base64.java @@ -0,0 +1,524 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +/** + * Provides Base64 encoding and decoding as defined by RFC 2045. + * + * <p>This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> + * from RFC 2045 <cite>Multipurpose Internet Mail Extensions (MIME) Part One: + * Format of Internet Message Bodies</cite> by Freed and Borenstein.</p> + * + * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> + * @author Apache Software Foundation + * @since 1.0-dev + * @version $Id: Base64.java,v 1.20 2004/05/24 00:21:24 ggregory Exp $ + */ +public class Base64 implements BinaryEncoder, BinaryDecoder { + + /** + * Chunk size per RFC 2045 section 6.8. + * + * <p>The {@value} character limit does not count the trailing CRLF, but counts + * all other characters, including any equal signs.</p> + * + * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a> + */ + static final int CHUNK_SIZE = 76; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a> + */ + static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes(); + + /** + * The base length. + */ + static final int BASELENGTH = 255; + + /** + * Lookup length. + */ + static final int LOOKUPLENGTH = 64; + + /** + * Used to calculate the number of bits in a byte. + */ + static final int EIGHTBIT = 8; + + /** + * Used when encoding something which has fewer than 24 bits. + */ + static final int SIXTEENBIT = 16; + + /** + * Used to determine how many bits data contains. + */ + static final int TWENTYFOURBITGROUP = 24; + + /** + * Used to get the number of Quadruples. + */ + static final int FOURBYTE = 4; + + /** + * Used to test the sign of a byte. + */ + static final int SIGN = -128; + + /** + * Byte used to pad output. + */ + static final byte PAD = (byte) '='; + + // Create arrays to hold the base64 characters and a + // lookup for base64 chars + private static byte[] base64Alphabet = new byte[BASELENGTH]; + private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH]; + + // Populating the lookup and character arrays + static { + for (int i = 0; i < BASELENGTH; i++) { + base64Alphabet[i] = (byte) -1; + } + for (int i = 'Z'; i >= 'A'; i--) { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + for (int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) { + lookUpBase64Alphabet[i] = (byte) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) { + lookUpBase64Alphabet[i] = (byte) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) { + lookUpBase64Alphabet[i] = (byte) ('0' + j); + } + + lookUpBase64Alphabet[62] = (byte) '+'; + lookUpBase64Alphabet[63] = (byte) '/'; + } + + private static boolean isBase64(byte octect) { + if (octect == PAD) { + return true; + } else if (base64Alphabet[octect] == -1) { + return false; + } else { + return true; + } + } + + /** + * Tests a given byte array to see if it contains + * only valid characters within the Base64 alphabet. + * + * @param arrayOctect byte array to test + * @return true if all bytes are valid characters in the Base64 + * alphabet or if the byte array is empty; false, otherwise + */ + public static boolean isArrayByteBase64(byte[] arrayOctect) { + + arrayOctect = discardWhitespace(arrayOctect); + + int length = arrayOctect.length; + if (length == 0) { + // shouldn't a 0 length array be valid base64 data? + // return false; + return true; + } + for (int i = 0; i < length; i++) { + if (!isBase64(arrayOctect[i])) { + return false; + } + } + return true; + } + + /** + * Encodes binary data using the base64 algorithm but + * does not chunk the output. + * + * @param binaryData binary data to encode + * @return Base64 characters + */ + public static byte[] encodeBase64(byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm and chunks + * the encoded output into 76 character blocks + * + * @param binaryData binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + + /** + * Decodes an Object using the base64 algorithm. This method + * is provided in order to satisfy the requirements of the + * Decoder interface, and will throw a DecoderException if the + * supplied object is not of type byte[]. + * + * @param pObject Object to decode + * @return An object (of type byte[]) containing the + * binary data which corresponds to the byte[] supplied. + * @throws DecoderException if the parameter supplied is not + * of type byte[] + */ + public Object decode(Object pObject) throws DecoderException { + if (!(pObject instanceof byte[])) { + throw new DecoderException("Parameter supplied to Base64 decode is not a byte[]"); + } + return decode((byte[]) pObject); + } + + /** + * Decodes a byte[] containing containing + * characters in the Base64 alphabet. + * + * @param pArray A byte array containing Base64 character data + * @return a byte array containing binary data + */ + public byte[] decode(byte[] pArray) { + return decodeBase64(pArray); + } + + /** + * Encodes binary data using the base64 algorithm, optionally + * chunking the output into 76 character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if isChunked is true this encoder will chunk + * the base64 output into 76 character blocks + * @return Base64-encoded data. + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { + int lengthDataBits = binaryData.length * EIGHTBIT; + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + byte encodedData[] = null; + int encodedDataLength = 0; + int nbrChunks = 0; + + if (fewerThan24bits != 0) { + //data not divisible by 24 bit + encodedDataLength = (numberTriplets + 1) * 4; + } else { + // 16 or 8 bit + encodedDataLength = numberTriplets * 4; + } + + // If the output is to be "chunked" into 76 character sections, + // for compliance with RFC 2045 MIME, then it is important to + // allow for extra length to account for the separator(s) + if (isChunked) { + + nbrChunks = + (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math.ceil((float) encodedDataLength / CHUNK_SIZE)); + encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length; + } + + encodedData = new byte[encodedDataLength]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + int i = 0; + int nextSeparatorIndex = CHUNK_SIZE; + int chunksSoFar = 0; + + //log.debug("number of triplets = " + numberTriplets); + for (i = 0; i < numberTriplets; i++) { + dataIndex = i * 3; + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + b3 = binaryData[dataIndex + 2]; + + //log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3); + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = + ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = + ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = + ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + //log.debug( "val2 = " + val2 ); + //log.debug( "k4 = " + (k<<4) ); + //log.debug( "vak = " + (val2 | (k<<4)) ); + encodedData[encodedIndex + 1] = + lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex + 2] = + lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f]; + + encodedIndex += 4; + + // If we are chunking, let's put a chunk separator down. + if (isChunked) { + // this assumes that CHUNK_SIZE % 4 == 0 + if (encodedIndex == nextSeparatorIndex) { + System.arraycopy( + CHUNK_SEPARATOR, + 0, + encodedData, + encodedIndex, + CHUNK_SEPARATOR.length); + chunksSoFar++; + nextSeparatorIndex = + (CHUNK_SIZE * (chunksSoFar + 1)) + + (chunksSoFar * CHUNK_SEPARATOR.length); + encodedIndex += CHUNK_SEPARATOR.length; + } + } + } + + // form integral number of 6-bit groups + dataIndex = i * 3; + + if (fewerThan24bits == EIGHTBIT) { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + //log.debug("b1=" + b1); + //log.debug("b1<<2 = " + (b1>>2) ); + byte val1 = + ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex + 2] = PAD; + encodedData[encodedIndex + 3] = PAD; + } else if (fewerThan24bits == SIXTEENBIT) { + + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = + ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = + ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex + 1] = + lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex + 3] = PAD; + } + + if (isChunked) { + // we also add a separator to the end of the final chunk. + if (chunksSoFar < nbrChunks) { + System.arraycopy( + CHUNK_SEPARATOR, + 0, + encodedData, + encodedDataLength - CHUNK_SEPARATOR.length, + CHUNK_SEPARATOR.length); + } + } + + return encodedData; + } + + /** + * Decodes Base64 data into octects + * + * @param base64Data Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(byte[] base64Data) { + // RFC 2045 requires that we discard ALL non-Base64 characters + base64Data = discardNonBase64(base64Data); + + // handle the edge case, so we don't have to worry about it later + if (base64Data.length == 0) { + return new byte[0]; + } + + int numberQuadruple = base64Data.length / FOURBYTE; + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0; + + // Throw away anything not in base64Data + + int encodedIndex = 0; + int dataIndex = 0; + { + // this sizes the output array properly - rlw + int lastData = base64Data.length; + // ignore the '=' padding + while (base64Data[lastData - 1] == PAD) { + if (--lastData == 0) { + return new byte[0]; + } + } + decodedData = new byte[lastData - numberQuadruple]; + } + + for (int i = 0; i < numberQuadruple; i++) { + dataIndex = i * 4; + marker0 = base64Data[dataIndex + 2]; + marker1 = base64Data[dataIndex + 3]; + + b1 = base64Alphabet[base64Data[dataIndex]]; + b2 = base64Alphabet[base64Data[dataIndex + 1]]; + + if (marker0 != PAD && marker1 != PAD) { + //No PAD e.g 3cQl + b3 = base64Alphabet[marker0]; + b4 = base64Alphabet[marker1]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); + } else if (marker0 == PAD) { + //Two PAD e.g. 3c[Pad][Pad] + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + } else if (marker1 == PAD) { + //One PAD e.g. 3cQ[Pad] + b3 = base64Alphabet[marker0]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + } + encodedIndex += 3; + } + return decodedData; + } + + /** + * Discards any whitespace from a base-64 encoded block. + * + * @param data The base-64 encoded data to discard the whitespace + * from. + * @return The data, less whitespace (see RFC 2045). + */ + static byte[] discardWhitespace(byte[] data) { + byte groomedData[] = new byte[data.length]; + int bytesCopied = 0; + + for (int i = 0; i < data.length; i++) { + switch (data[i]) { + case (byte) ' ' : + case (byte) '\n' : + case (byte) '\r' : + case (byte) '\t' : + break; + default: + groomedData[bytesCopied++] = data[i]; + } + } + + byte packedData[] = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } + + /** + * Discards any characters outside of the base64 alphabet, per + * the requirements on page 25 of RFC 2045 - "Any characters + * outside of the base64 alphabet are to be ignored in base64 + * encoded data." + * + * @param data The base-64 encoded data to groom + * @return The data, less non-base64 characters (see RFC 2045). + */ + static byte[] discardNonBase64(byte[] data) { + byte groomedData[] = new byte[data.length]; + int bytesCopied = 0; + + for (int i = 0; i < data.length; i++) { + if (isBase64(data[i])) { + groomedData[bytesCopied++] = data[i]; + } + } + + byte packedData[] = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } + + + // Implementation of the Encoder Interface + + /** + * Encodes an Object using the base64 algorithm. This method + * is provided in order to satisfy the requirements of the + * Encoder interface, and will throw an EncoderException if the + * supplied object is not of type byte[]. + * + * @param pObject Object to encode + * @return An object (of type byte[]) containing the + * base64 encoded data which corresponds to the byte[] supplied. + * @throws EncoderException if the parameter supplied is not + * of type byte[] + */ + public Object encode(Object pObject) throws EncoderException { + if (!(pObject instanceof byte[])) { + throw new EncoderException( + "Parameter supplied to Base64 encode is not a byte[]"); + } + return encode((byte[]) pObject); + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing + * characters in the Base64 alphabet. + * + * @param pArray a byte array containing binary data + * @return A byte array containing only Base64 character data + */ + public byte[] encode(byte[] pArray) { + return encodeBase64(pArray, false); + } + +} diff --git a/src/org/apache/commons/codec/binary/BinaryCodec.java b/src/org/apache/commons/codec/binary/BinaryCodec.java new file mode 100644 index 0000000..98c6409 --- /dev/null +++ b/src/org/apache/commons/codec/binary/BinaryCodec.java @@ -0,0 +1,285 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +/** + * Translates between byte arrays and strings of "0"s and "1"s. + * + * <b>TODO:</b> may want to add more bit vector functions like and/or/xor/nand. + * <B>TODO:</b> also might be good to generate boolean[] + * from byte[] et. cetera. + * + * @author Apache Software Foundation + * @since 1.3 + * @version $Id $ + */ +public class BinaryCodec implements BinaryDecoder, BinaryEncoder { + /* + * tried to avoid using ArrayUtils to minimize dependencies while using these empty arrays - dep is just not worth + * it. + */ + /** Empty char array. */ + private static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + /** Empty byte array. */ + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** Mask for bit 0 of a byte. */ + private static final int BIT_0 = 1; + + /** Mask for bit 1 of a byte. */ + private static final int BIT_1 = 0x02; + + /** Mask for bit 2 of a byte. */ + private static final int BIT_2 = 0x04; + + /** Mask for bit 3 of a byte. */ + private static final int BIT_3 = 0x08; + + /** Mask for bit 4 of a byte. */ + private static final int BIT_4 = 0x10; + + /** Mask for bit 5 of a byte. */ + private static final int BIT_5 = 0x20; + + /** Mask for bit 6 of a byte. */ + private static final int BIT_6 = 0x40; + + /** Mask for bit 7 of a byte. */ + private static final int BIT_7 = 0x80; + + private static final int[] BITS = {BIT_0, BIT_1, BIT_2, BIT_3, BIT_4, BIT_5, BIT_6, BIT_7}; + + /** + * Converts an array of raw binary data into an array of ascii 0 and 1 characters. + * + * @param raw + * the raw binary data to convert + * @return 0 and 1 ascii character bytes one for each bit of the argument + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + public byte[] encode(byte[] raw) { + return toAsciiBytes(raw); + } + + /** + * Converts an array of raw binary data into an array of ascii 0 and 1 chars. + * + * @param raw + * the raw binary data to convert + * @return 0 and 1 ascii character chars one for each bit of the argument + * @throws EncoderException + * if the argument is not a byte[] + * @see org.apache.commons.codec.Encoder#encode(java.lang.Object) + */ + public Object encode(Object raw) throws EncoderException { + if (!(raw instanceof byte[])) { + throw new EncoderException("argument not a byte array"); + } + return toAsciiChars((byte[]) raw); + } + + /** + * Decodes a byte array where each byte represents an ascii '0' or '1'. + * + * @param ascii + * each byte represents an ascii '0' or '1' + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + * @throws DecoderException + * if argument is not a byte[], char[] or String + * @see org.apache.commons.codec.Decoder#decode(java.lang.Object) + */ + public Object decode(Object ascii) throws DecoderException { + if (ascii == null) { + return EMPTY_BYTE_ARRAY; + } + if (ascii instanceof byte[]) { + return fromAscii((byte[]) ascii); + } + if (ascii instanceof char[]) { + return fromAscii((char[]) ascii); + } + if (ascii instanceof String) { + return fromAscii(((String) ascii).toCharArray()); + } + throw new DecoderException("argument not a byte array"); + } + + /** + * Decodes a byte array where each byte represents an ascii '0' or '1'. + * + * @param ascii + * each byte represents an ascii '0' or '1' + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + * @see org.apache.commons.codec.Decoder#decode(Object) + */ + public byte[] decode(byte[] ascii) { + return fromAscii(ascii); + } + + /** + * Decodes a String where each char of the String represents an ascii '0' or '1'. + * + * @param ascii + * String of '0' and '1' characters + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + * @see org.apache.commons.codec.Decoder#decode(Object) + */ + public byte[] toByteArray(String ascii) { + if (ascii == null) { + return EMPTY_BYTE_ARRAY; + } + return fromAscii(ascii.toCharArray()); + } + + // ------------------------------------------------------------------------ + // + // static codec operations + // + // ------------------------------------------------------------------------ + /** + * Decodes a byte array where each char represents an ascii '0' or '1'. + * + * @param ascii + * each char represents an ascii '0' or '1' + * @return the raw encoded binary where each bit corresponds to a char in the char array argument + */ + public static byte[] fromAscii(char[] ascii) { + if (ascii == null || ascii.length == 0) { + return EMPTY_BYTE_ARRAY; + } + // get length/8 times bytes with 3 bit shifts to the right of the length + byte[] l_raw = new byte[ascii.length >> 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if (ascii[jj - bits] == '1') { + l_raw[ii] |= BITS[bits]; + } + } + } + return l_raw; + } + + /** + * Decodes a byte array where each byte represents an ascii '0' or '1'. + * + * @param ascii + * each byte represents an ascii '0' or '1' + * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument + */ + public static byte[] fromAscii(byte[] ascii) { + if (ascii == null || ascii.length == 0) { + return EMPTY_BYTE_ARRAY; + } + // get length/8 times bytes with 3 bit shifts to the right of the length + byte[] l_raw = new byte[ascii.length >> 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if (ascii[jj - bits] == '1') { + l_raw[ii] |= BITS[bits]; + } + } + } + return l_raw; + } + + /** + * Converts an array of raw binary data into an array of ascii 0 and 1 character bytes - each byte is a truncated + * char. + * + * @param raw + * the raw binary data to convert + * @return an array of 0 and 1 character bytes for each bit of the argument + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + public static byte[] toAsciiBytes(byte[] raw) { + if (raw == null || raw.length == 0) { + return EMPTY_BYTE_ARRAY; + } + // get 8 times the bytes with 3 bit shifts to the left of the length + byte[] l_ascii = new byte[raw.length << 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if ((raw[ii] & BITS[bits]) == 0) { + l_ascii[jj - bits] = '0'; + } else { + l_ascii[jj - bits] = '1'; + } + } + } + return l_ascii; + } + + /** + * Converts an array of raw binary data into an array of ascii 0 and 1 characters. + * + * @param raw + * the raw binary data to convert + * @return an array of 0 and 1 characters for each bit of the argument + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + public static char[] toAsciiChars(byte[] raw) { + if (raw == null || raw.length == 0) { + return EMPTY_CHAR_ARRAY; + } + // get 8 times the bytes with 3 bit shifts to the left of the length + char[] l_ascii = new char[raw.length << 3]; + /* + * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the + * loop. + */ + for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) { + for (int bits = 0; bits < BITS.length; ++bits) { + if ((raw[ii] & BITS[bits]) == 0) { + l_ascii[jj - bits] = '0'; + } else { + l_ascii[jj - bits] = '1'; + } + } + } + return l_ascii; + } + + /** + * Converts an array of raw binary data into a String of ascii 0 and 1 characters. + * + * @param raw + * the raw binary data to convert + * @return a String of 0 and 1 characters representing the binary data + * @see org.apache.commons.codec.BinaryEncoder#encode(byte[]) + */ + public static String toAsciiString(byte[] raw) { + return new String(toAsciiChars(raw)); + } +} diff --git a/src/org/apache/commons/codec/binary/Hex.java b/src/org/apache/commons/codec/binary/Hex.java new file mode 100644 index 0000000..78f5510 --- /dev/null +++ b/src/org/apache/commons/codec/binary/Hex.java @@ -0,0 +1,192 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.binary; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +/** + * Hex encoder and decoder. + * + * @since 1.1 + * @author Apache Software Foundation + * @version $Id: Hex.java,v 1.13 2004/04/18 18:22:33 ggregory Exp $ + */ +public class Hex implements BinaryEncoder, BinaryDecoder { + + /** + * Used building output as Hex + */ + private static final char[] DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + /** + * Converts an array of characters representing hexidecimal values into an + * array of bytes of those same values. The returned array will be half the + * length of the passed array, as it takes two characters to represent any + * given byte. An exception is thrown if the passed char array has an odd + * number of elements. + * + * @param data An array of characters containing hexidecimal digits + * @return A byte array containing binary data decoded from + * the supplied char array. + * @throws DecoderException Thrown if an odd number or illegal of characters + * is supplied + */ + public static byte[] decodeHex(char[] data) throws DecoderException { + + int len = data.length; + + if ((len & 0x01) != 0) { + throw new DecoderException("Odd number of characters."); + } + + byte[] out = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) { + int f = toDigit(data[j], j) << 4; + j++; + f = f | toDigit(data[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + /** + * Converts a hexadecimal character to an integer. + * + * @param ch A character to convert to an integer digit + * @param index The index of the character in the source + * @return An integer + * @throws DecoderException Thrown if ch is an illegal hex character + */ + protected static int toDigit(char ch, int index) throws DecoderException { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new DecoderException("Illegal hexadecimal charcter " + ch + " at index " + index); + } + return digit; + } + + /** + * Converts an array of bytes into an array of characters representing the hexidecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @return A char[] containing hexidecimal characters + */ + public static char[] encodeHex(byte[] data) { + + int l = data.length; + + char[] out = new char[l << 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = DIGITS[(0xF0 & data[i]) >>> 4 ]; + out[j++] = DIGITS[ 0x0F & data[i] ]; + } + + return out; + } + + /** + * Converts an array of character bytes representing hexidecimal values into an + * array of bytes of those same values. The returned array will be half the + * length of the passed array, as it takes two characters to represent any + * given byte. An exception is thrown if the passed char array has an odd + * number of elements. + * + * @param array An array of character bytes containing hexidecimal digits + * @return A byte array containing binary data decoded from + * the supplied byte array (representing characters). + * @throws DecoderException Thrown if an odd number of characters is supplied + * to this function + * @see #decodeHex(char[]) + */ + public byte[] decode(byte[] array) throws DecoderException { + return decodeHex(new String(array).toCharArray()); + } + + /** + * Converts a String or an array of character bytes representing hexidecimal values into an + * array of bytes of those same values. The returned array will be half the + * length of the passed String or array, as it takes two characters to represent any + * given byte. An exception is thrown if the passed char array has an odd + * number of elements. + * + * @param object A String or, an array of character bytes containing hexidecimal digits + * @return A byte array containing binary data decoded from + * the supplied byte array (representing characters). + * @throws DecoderException Thrown if an odd number of characters is supplied + * to this function or the object is not a String or char[] + * @see #decodeHex(char[]) + */ + public Object decode(Object object) throws DecoderException { + try { + char[] charArray = object instanceof String ? ((String) object).toCharArray() : (char[]) object; + return decodeHex(charArray); + } catch (ClassCastException e) { + throw new DecoderException(e.getMessage()); + } + } + + /** + * Converts an array of bytes into an array of bytes for the characters representing the + * hexidecimal values of each byte in order. The returned array will be + * double the length of the passed array, as it takes two characters to + * represent any given byte. + * + * @param array a byte[] to convert to Hex characters + * @return A byte[] containing the bytes of the hexidecimal characters + * @see #encodeHex(byte[]) + */ + public byte[] encode(byte[] array) { + return new String(encodeHex(array)).getBytes(); + } + + /** + * Converts a String or an array of bytes into an array of characters representing the + * hexidecimal values of each byte in order. The returned array will be + * double the length of the passed String or array, as it takes two characters to + * represent any given byte. + * + * @param object a String, or byte[] to convert to Hex characters + * @return A char[] containing hexidecimal characters + * @throws EncoderException Thrown if the given object is not a String or byte[] + * @see #encodeHex(byte[]) + */ + public Object encode(Object object) throws EncoderException { + try { + byte[] byteArray = object instanceof String ? ((String) object).getBytes() : (byte[]) object; + return encodeHex(byteArray); + } catch (ClassCastException e) { + throw new EncoderException(e.getMessage()); + } + } + +} + diff --git a/src/org/apache/commons/codec/binary/package.html b/src/org/apache/commons/codec/binary/package.html new file mode 100644 index 0000000..844d918 --- /dev/null +++ b/src/org/apache/commons/codec/binary/package.html @@ -0,0 +1,20 @@ +<!-- +Copyright 2003-2004 The Apache Software Foundation. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<html> + <body> + Base64, Binary, and Hexadecimal String encoding and decoding. + </body> +</html> diff --git a/src/org/apache/commons/codec/language/DoubleMetaphone.java b/src/org/apache/commons/codec/language/DoubleMetaphone.java new file mode 100644 index 0000000..1cad991 --- /dev/null +++ b/src/org/apache/commons/codec/language/DoubleMetaphone.java @@ -0,0 +1,1103 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.language; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringEncoder; + +/** + * Encodes a string into a double metaphone value. + * This Implementation is based on the algorithm by <CITE>Lawrence Philips</CITE>. + * <ul> + * <li>Original Article: <a + * href="http://www.cuj.com/documents/s=8038/cuj0006philips/"> + * http://www.cuj.com/documents/s=8038/cuj0006philips/</a></li> + * <li>Original Source Code: <a href="ftp://ftp.cuj.com/pub/2000/1806/philips.zip"> + * ftp://ftp.cuj.com/pub/2000/1806/philips.zip</a></li> + * </ul> + * + * @author Apache Software Foundation + * @version $Id: DoubleMetaphone.java,v 1.24 2004/06/05 18:32:04 ggregory Exp $ + */ +public class DoubleMetaphone implements StringEncoder { + + /** + * "Vowels" to test for + */ + private static final String VOWELS = "AEIOUY"; + + /** + * Prefixes when present which are not pronounced + */ + private static final String[] SILENT_START = + { "GN", "KN", "PN", "WR", "PS" }; + private static final String[] L_R_N_M_B_H_F_V_W_SPACE = + { "L", "R", "N", "M", "B", "H", "F", "V", "W", " " }; + private static final String[] ES_EP_EB_EL_EY_IB_IL_IN_IE_EI_ER = + { "ES", "EP", "EB", "EL", "EY", "IB", "IL", "IN", "IE", "EI", "ER" }; + private static final String[] L_T_K_S_N_M_B_Z = + { "L", "T", "K", "S", "N", "M", "B", "Z" }; + + /** + * Maximum length of an encoding, default is 4 + */ + protected int maxCodeLen = 4; + + /** + * Creates an instance of this DoubleMetaphone encoder + */ + public DoubleMetaphone() { + super(); + } + + /** + * Encode a value with Double Metaphone + * + * @param value String to encode + * @return an encoded string + */ + public String doubleMetaphone(String value) { + return doubleMetaphone(value, false); + } + + /** + * Encode a value with Double Metaphone, optionally using the alternate + * encoding. + * + * @param value String to encode + * @param alternate use alternate encode + * @return an encoded string + */ + public String doubleMetaphone(String value, boolean alternate) { + value = cleanInput(value); + if (value == null) { + return null; + } + + boolean slavoGermanic = isSlavoGermanic(value); + int index = isSilentStart(value) ? 1 : 0; + + DoubleMetaphoneResult result = new DoubleMetaphoneResult(this.getMaxCodeLen()); + + while (!result.isComplete() && index <= value.length() - 1) { + switch (value.charAt(index)) { + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + case 'Y': + index = handleAEIOUY(value, result, index); + break; + case 'B': + result.append('P'); + index = charAt(value, index + 1) == 'B' ? index + 2 : index + 1; + break; + case '\u00C7': + // A C with a Cedilla + result.append('S'); + index++; + break; + case 'C': + index = handleC(value, result, index); + break; + case 'D': + index = handleD(value, result, index); + break; + case 'F': + result.append('F'); + index = charAt(value, index + 1) == 'F' ? index + 2 : index + 1; + break; + case 'G': + index = handleG(value, result, index, slavoGermanic); + break; + case 'H': + index = handleH(value, result, index); + break; + case 'J': + index = handleJ(value, result, index, slavoGermanic); + break; + case 'K': + result.append('K'); + index = charAt(value, index + 1) == 'K' ? index + 2 : index + 1; + break; + case 'L': + index = handleL(value, result, index); + break; + case 'M': + result.append('M'); + index = conditionM0(value, index) ? index + 2 : index + 1; + break; + case 'N': + result.append('N'); + index = charAt(value, index + 1) == 'N' ? index + 2 : index + 1; + break; + case '\u00D1': + // N with a tilde (spanish ene) + result.append('N'); + index++; + break; + case 'P': + index = handleP(value, result, index); + break; + case 'Q': + result.append('K'); + index = charAt(value, index + 1) == 'Q' ? index + 2 : index + 1; + break; + case 'R': + index = handleR(value, result, index, slavoGermanic); + break; + case 'S': + index = handleS(value, result, index, slavoGermanic); + break; + case 'T': + index = handleT(value, result, index); + break; + case 'V': + result.append('F'); + index = charAt(value, index + 1) == 'V' ? index + 2 : index + 1; + break; + case 'W': + index = handleW(value, result, index); + break; + case 'X': + index = handleX(value, result, index); + break; + case 'Z': + index = handleZ(value, result, index, slavoGermanic); + break; + default: + index++; + break; + } + } + + return alternate ? result.getAlternate() : result.getPrimary(); + } + + /** + * Encode the value using DoubleMetaphone. It will only work if + * <code>obj</code> is a <code>String</code> (like <code>Metaphone</code>). + * + * @param obj Object to encode (should be of type String) + * @return An encoded Object (will be of type String) + * @throws EncoderException encode parameter is not of type String + */ + public Object encode(Object obj) throws EncoderException { + if (!(obj instanceof String)) { + throw new EncoderException("DoubleMetaphone encode parameter is not of type String"); + } + return doubleMetaphone((String) obj); + } + + /** + * Encode the value using DoubleMetaphone. + * + * @param value String to encode + * @return An encoded String + */ + public String encode(String value) { + return doubleMetaphone(value); + } + + /** + * Check if the Double Metaphone values of two <code>String</code> values + * are equal. + * + * @param value1 The left-hand side of the encoded {@link String#equals(Object)}. + * @param value2 The right-hand side of the encoded {@link String#equals(Object)}. + * @return <code>true</code> if the encoded <code>String</code>s are equal; + * <code>false</code> otherwise. + * @see #isDoubleMetaphoneEqual(String,String,boolean) + */ + public boolean isDoubleMetaphoneEqual(String value1, String value2) { + return isDoubleMetaphoneEqual(value1, value2, false); + } + + /** + * Check if the Double Metaphone values of two <code>String</code> values + * are equal, optionally using the alternate value. + * + * @param value1 The left-hand side of the encoded {@link String#equals(Object)}. + * @param value2 The right-hand side of the encoded {@link String#equals(Object)}. + * @param alternate use the alternate value if <code>true</code>. + * @return <code>true</code> if the encoded <code>String</code>s are equal; + * <code>false</code> otherwise. + */ + public boolean isDoubleMetaphoneEqual(String value1, + String value2, + boolean alternate) { + return doubleMetaphone(value1, alternate).equals(doubleMetaphone + (value2, alternate)); + } + + /** + * Returns the maxCodeLen. + * @return int + */ + public int getMaxCodeLen() { + return this.maxCodeLen; + } + + /** + * Sets the maxCodeLen. + * @param maxCodeLen The maxCodeLen to set + */ + public void setMaxCodeLen(int maxCodeLen) { + this.maxCodeLen = maxCodeLen; + } + + //-- BEGIN HANDLERS --// + + /** + * Handles 'A', 'E', 'I', 'O', 'U', and 'Y' cases + */ + private int handleAEIOUY(String value, DoubleMetaphoneResult result, int + index) { + if (index == 0) { + result.append('A'); + } + return index + 1; + } + + /** + * Handles 'C' cases + */ + private int handleC(String value, + DoubleMetaphoneResult result, + int index) { + if (conditionC0(value, index)) { // very confusing, moved out + result.append('K'); + index += 2; + } else if (index == 0 && contains(value, index, 6, "CAESAR")) { + result.append('S'); + index += 2; + } else if (contains(value, index, 2, "CH")) { + index = handleCH(value, result, index); + } else if (contains(value, index, 2, "CZ") && + !contains(value, index - 2, 4, "WICZ")) { + //-- "Czerny" --// + result.append('S', 'X'); + index += 2; + } else if (contains(value, index + 1, 3, "CIA")) { + //-- "focaccia" --// + result.append('X'); + index += 3; + } else if (contains(value, index, 2, "CC") && + !(index == 1 && charAt(value, 0) == 'M')) { + //-- double "cc" but not "McClelland" --// + return handleCC(value, result, index); + } else if (contains(value, index, 2, "CK", "CG", "CQ")) { + result.append('K'); + index += 2; + } else if (contains(value, index, 2, "CI", "CE", "CY")) { + //-- Italian vs. English --// + if (contains(value, index, 3, "CIO", "CIE", "CIA")) { + result.append('S', 'X'); + } else { + result.append('S'); + } + index += 2; + } else { + result.append('K'); + if (contains(value, index + 1, 2, " C", " Q", " G")) { + //-- Mac Caffrey, Mac Gregor --// + index += 3; + } else if (contains(value, index + 1, 1, "C", "K", "Q") && + !contains(value, index + 1, 2, "CE", "CI")) { + index += 2; + } else { + index++; + } + } + + return index; + } + + /** + * Handles 'CC' cases + */ + private int handleCC(String value, + DoubleMetaphoneResult result, + int index) { + if (contains(value, index + 2, 1, "I", "E", "H") && + !contains(value, index + 2, 2, "HU")) { + //-- "bellocchio" but not "bacchus" --// + if ((index == 1 && charAt(value, index - 1) == 'A') || + contains(value, index - 1, 5, "UCCEE", "UCCES")) { + //-- "accident", "accede", "succeed" --// + result.append("KS"); + } else { + //-- "bacci", "bertucci", other Italian --// + result.append('X'); + } + index += 3; + } else { // Pierce's rule + result.append('K'); + index += 2; + } + + return index; + } + + /** + * Handles 'CH' cases + */ + private int handleCH(String value, + DoubleMetaphoneResult result, + int index) { + if (index > 0 && contains(value, index, 4, "CHAE")) { // Michael + result.append('K', 'X'); + return index + 2; + } else if (conditionCH0(value, index)) { + //-- Greek roots ("chemistry", "chorus", etc.) --// + result.append('K'); + return index + 2; + } else if (conditionCH1(value, index)) { + //-- Germanic, Greek, or otherwise 'ch' for 'kh' sound --// + result.append('K'); + return index + 2; + } else { + if (index > 0) { + if (contains(value, 0, 2, "MC")) { + result.append('K'); + } else { + result.append('X', 'K'); + } + } else { + result.append('X'); + } + return index + 2; + } + } + + /** + * Handles 'D' cases + */ + private int handleD(String value, + DoubleMetaphoneResult result, + int index) { + if (contains(value, index, 2, "DG")) { + //-- "Edge" --// + if (contains(value, index + 2, 1, "I", "E", "Y")) { + result.append('J'); + index += 3; + //-- "Edgar" --// + } else { + result.append("TK"); + index += 2; + } + } else if (contains(value, index, 2, "DT", "DD")) { + result.append('T'); + index += 2; + } else { + result.append('T'); + index++; + } + return index; + } + + /** + * Handles 'G' cases + */ + private int handleG(String value, + DoubleMetaphoneResult result, + int index, + boolean slavoGermanic) { + if (charAt(value, index + 1) == 'H') { + index = handleGH(value, result, index); + } else if (charAt(value, index + 1) == 'N') { + if (index == 1 && isVowel(charAt(value, 0)) && !slavoGermanic) { + result.append("KN", "N"); + } else if (!contains(value, index + 2, 2, "EY") && + charAt(value, index + 1) != 'Y' && !slavoGermanic) { + result.append("N", "KN"); + } else { + result.append("KN"); + } + index = index + 2; + } else if (contains(value, index + 1, 2, "LI") && !slavoGermanic) { + result.append("KL", "L"); + index += 2; + } else if (index == 0 && (charAt(value, index + 1) == 'Y' || contains(value, index + 1, 2, ES_EP_EB_EL_EY_IB_IL_IN_IE_EI_ER))) { + //-- -ges-, -gep-, -gel-, -gie- at beginning --// + result.append('K', 'J'); + index += 2; + } else if ((contains(value, index + 1, 2, "ER") || + charAt(value, index + 1) == 'Y') && + !contains(value, 0, 6, "DANGER", "RANGER", "MANGER") && + !contains(value, index - 1, 1, "E", "I") && + !contains(value, index - 1, 3, "RGY", "OGY")) { + //-- -ger-, -gy- --// + result.append('K', 'J'); + index += 2; + } else if (contains(value, index + 1, 1, "E", "I", "Y") || + contains(value, index - 1, 4, "AGGI", "OGGI")) { + //-- Italian "biaggi" --// + if ((contains(value, 0 ,4, "VAN ", "VON ") || contains(value, 0, 3, "SCH")) || contains(value, index + 1, 2, "ET")) { + //-- obvious germanic --// + result.append('K'); + } else if (contains(value, index + 1, 4, "IER")) { + result.append('J'); + } else { + result.append('J', 'K'); + } + index += 2; + } else if (charAt(value, index + 1) == 'G') { + index += 2; + result.append('K'); + } else { + index++; + result.append('K'); + } + return index; + } + + /** + * Handles 'GH' cases + */ + private int handleGH(String value, + DoubleMetaphoneResult result, + int index) { + if (index > 0 && !isVowel(charAt(value, index - 1))) { + result.append('K'); + index += 2; + } else if (index == 0) { + if (charAt(value, index + 2) == 'I') { + result.append('J'); + } else { + result.append('K'); + } + index += 2; + } else if ((index > 1 && contains(value, index - 2, 1, "B", "H", "D")) || + (index > 2 && contains(value, index - 3, 1, "B", "H", "D")) || + (index > 3 && contains(value, index - 4, 1, "B", "H"))) { + //-- Parker's rule (with some further refinements) - "hugh" + index += 2; + } else { + if (index > 2 && charAt(value, index - 1) == 'U' && + contains(value, index - 3, 1, "C", "G", "L", "R", "T")) { + //-- "laugh", "McLaughlin", "cough", "gough", "rough", "tough" + result.append('F'); + } else if (index > 0 && charAt(value, index - 1) != 'I') { + result.append('K'); + } + index += 2; + } + return index; + } + + /** + * Handles 'H' cases + */ + private int handleH(String value, + DoubleMetaphoneResult result, + int index) { + //-- only keep if first & before vowel or between 2 vowels --// + if ((index == 0 || isVowel(charAt(value, index - 1))) && + isVowel(charAt(value, index + 1))) { + result.append('H'); + index += 2; + //-- also takes car of "HH" --// + } else { + index++; + } + return index; + } + + /** + * Handles 'J' cases + */ + private int handleJ(String value, DoubleMetaphoneResult result, int index, + boolean slavoGermanic) { + if (contains(value, index, 4, "JOSE") || contains(value, 0, 4, "SAN ")) { + //-- obvious Spanish, "Jose", "San Jacinto" --// + if ((index == 0 && (charAt(value, index + 4) == ' ') || + value.length() == 4) || contains(value, 0, 4, "SAN ")) { + result.append('H'); + } else { + result.append('J', 'H'); + } + index++; + } else { + if (index == 0 && !contains(value, index, 4, "JOSE")) { + result.append('J', 'A'); + } else if (isVowel(charAt(value, index - 1)) && !slavoGermanic && + (charAt(value, index + 1) == 'A' || charAt(value, index + 1) == 'O')) { + result.append('J', 'H'); + } else if (index == value.length() - 1) { + result.append('J', ' '); + } else if (!contains(value, index + 1, 1, L_T_K_S_N_M_B_Z) && !contains(value, index - 1, 1, "S", "K", "L")) { + result.append('J'); + } + + if (charAt(value, index + 1) == 'J') { + index += 2; + } else { + index++; + } + } + return index; + } + + /** + * Handles 'L' cases + */ + private int handleL(String value, + DoubleMetaphoneResult result, + int index) { + result.append('L'); + if (charAt(value, index + 1) == 'L') { + if (conditionL0(value, index)) { + result.appendAlternate(' '); + } + index += 2; + } else { + index++; + } + return index; + } + + /** + * Handles 'P' cases + */ + private int handleP(String value, + DoubleMetaphoneResult result, + int index) { + if (charAt(value, index + 1) == 'H') { + result.append('F'); + index += 2; + } else { + result.append('P'); + index = contains(value, index + 1, 1, "P", "B") ? index + 2 : index + 1; + } + return index; + } + + /** + * Handles 'R' cases + */ + private int handleR(String value, + DoubleMetaphoneResult result, + int index, + boolean slavoGermanic) { + if (index == value.length() - 1 && !slavoGermanic && + contains(value, index - 2, 2, "IE") && + !contains(value, index - 4, 2, "ME", "MA")) { + result.appendAlternate('R'); + } else { + result.append('R'); + } + return charAt(value, index + 1) == 'R' ? index + 2 : index + 1; + } + + /** + * Handles 'S' cases + */ + private int handleS(String value, + DoubleMetaphoneResult result, + int index, + boolean slavoGermanic) { + if (contains(value, index - 1, 3, "ISL", "YSL")) { + //-- special cases "island", "isle", "carlisle", "carlysle" --// + index++; + } else if (index == 0 && contains(value, index, 5, "SUGAR")) { + //-- special case "sugar-" --// + result.append('X', 'S'); + index++; + } else if (contains(value, index, 2, "SH")) { + if (contains(value, index + 1, 4, + "HEIM", "HOEK", "HOLM", "HOLZ")) { + //-- germanic --// + result.append('S'); + } else { + result.append('X'); + } + index += 2; + } else if (contains(value, index, 3, "SIO", "SIA") || contains(value, index, 4, "SIAN")) { + //-- Italian and Armenian --// + if (slavoGermanic) { + result.append('S'); + } else { + result.append('S', 'X'); + } + index += 3; + } else if ((index == 0 && contains(value, index + 1, 1, "M", "N", "L", "W")) || contains(value, index + 1, 1, "Z")) { + //-- german & anglicisations, e.g. "smith" match "schmidt" // + // "snider" match "schneider" --// + //-- also, -sz- in slavic language altho in hungarian it // + // is pronounced "s" --// + result.append('S', 'X'); + index = contains(value, index + 1, 1, "Z") ? index + 2 : index + 1; + } else if (contains(value, index, 2, "SC")) { + index = handleSC(value, result, index); + } else { + if (index == value.length() - 1 && contains(value, index - 2, + 2, "AI", "OI")){ + //-- french e.g. "resnais", "artois" --// + result.appendAlternate('S'); + } else { + result.append('S'); + } + index = contains(value, index + 1, 1, "S", "Z") ? index + 2 : index + 1; + } + return index; + } + + /** + * Handles 'SC' cases + */ + private int handleSC(String value, + DoubleMetaphoneResult result, + int index) { + if (charAt(value, index + 2) == 'H') { + //-- Schlesinger's rule --// + if (contains(value, index + 3, + 2, "OO", "ER", "EN", "UY", "ED", "EM")) { + //-- Dutch origin, e.g. "school", "schooner" --// + if (contains(value, index + 3, 2, "ER", "EN")) { + //-- "schermerhorn", "schenker" --// + result.append("X", "SK"); + } else { + result.append("SK"); + } + } else { + if (index == 0 && !isVowel(charAt(value, 3)) && charAt(value, 3) != 'W') { + result.append('X', 'S'); + } else { + result.append('X'); + } + } + } else if (contains(value, index + 2, 1, "I", "E", "Y")) { + result.append('S'); + } else { + result.append("SK"); + } + return index + 3; + } + + /** + * Handles 'T' cases + */ + private int handleT(String value, + DoubleMetaphoneResult result, + int index) { + if (contains(value, index, 4, "TION")) { + result.append('X'); + index += 3; + } else if (contains(value, index, 3, "TIA", "TCH")) { + result.append('X'); + index += 3; + } else if (contains(value, index, 2, "TH") || contains(value, index, + 3, "TTH")) { + if (contains(value, index + 2, 2, "OM", "AM") || + //-- special case "thomas", "thames" or germanic --// + contains(value, 0, 4, "VAN ", "VON ") || + contains(value, 0, 3, "SCH")) { + result.append('T'); + } else { + result.append('0', 'T'); + } + index += 2; + } else { + result.append('T'); + index = contains(value, index + 1, 1, "T", "D") ? index + 2 : index + 1; + } + return index; + } + + /** + * Handles 'W' cases + */ + private int handleW(String value, + DoubleMetaphoneResult result, + int index) { + if (contains(value, index, 2, "WR")) { + //-- can also be in middle of word --// + result.append('R'); + index += 2; + } else { + if (index == 0 && (isVowel(charAt(value, index + 1)) || + contains(value, index, 2, "WH"))) { + if (isVowel(charAt(value, index + 1))) { + //-- Wasserman should match Vasserman --// + result.append('A', 'F'); + } else { + //-- need Uomo to match Womo --// + result.append('A'); + } + index++; + } else if ((index == value.length() - 1 && isVowel(charAt(value, index - 1))) || + contains(value, index - 1, + 5, "EWSKI", "EWSKY", "OWSKI", "OWSKY") || + contains(value, 0, 3, "SCH")) { + //-- Arnow should match Arnoff --// + result.appendAlternate('F'); + index++; + } else if (contains(value, index, 4, "WICZ", "WITZ")) { + //-- Polish e.g. "filipowicz" --// + result.append("TS", "FX"); + index += 4; + } else { + index++; + } + } + return index; + } + + /** + * Handles 'X' cases + */ + private int handleX(String value, + DoubleMetaphoneResult result, + int index) { + if (index == 0) { + result.append('S'); + index++; + } else { + if (!((index == value.length() - 1) && + (contains(value, index - 3, 3, "IAU", "EAU") || + contains(value, index - 2, 2, "AU", "OU")))) { + //-- French e.g. breaux --// + result.append("KS"); + } + index = contains(value, index + 1, 1, "C", "X") ? index + 2 : index + 1; + } + return index; + } + + /** + * Handles 'Z' cases + */ + private int handleZ(String value, DoubleMetaphoneResult result, int index, + boolean slavoGermanic) { + if (charAt(value, index + 1) == 'H') { + //-- Chinese pinyin e.g. "zhao" or Angelina "Zhang" --// + result.append('J'); + index += 2; + } else { + if (contains(value, index + 1, 2, "ZO", "ZI", "ZA") || (slavoGermanic && (index > 0 && charAt(value, index - 1) != 'T'))) { + result.append("S", "TS"); + } else { + result.append('S'); + } + index = charAt(value, index + 1) == 'Z' ? index + 2 : index + 1; + } + return index; + } + + //-- BEGIN CONDITIONS --// + + /** + * Complex condition 0 for 'C' + */ + private boolean conditionC0(String value, int index) { + if (contains(value, index, 4, "CHIA")) { + return true; + } else if (index <= 1) { + return false; + } else if (isVowel(charAt(value, index - 2))) { + return false; + } else if (!contains(value, index - 1, 3, "ACH")) { + return false; + } else { + char c = charAt(value, index + 2); + return (c != 'I' && c != 'E') + || contains(value, index - 2, 6, "BACHER", "MACHER"); + } + } + + /** + * Complex condition 0 for 'CH' + */ + private boolean conditionCH0(String value, int index) { + if (index != 0) { + return false; + } else if (!contains(value, index + 1, 5, "HARAC", "HARIS") && + !contains(value, index + 1, 3, "HOR", "HYM", "HIA", "HEM")) { + return false; + } else if (contains(value, 0, 5, "CHORE")) { + return false; + } else { + return true; + } + } + + /** + * Complex condition 1 for 'CH' + */ + private boolean conditionCH1(String value, int index) { + return ((contains(value, 0, 4, "VAN ", "VON ") || contains(value, 0, + 3, "SCH")) || + contains(value, index - 2, 6, "ORCHES", "ARCHIT", "ORCHID") || + contains(value, index + 2, 1, "T", "S") || + ((contains(value, index - 1, 1, "A", "O", "U", "E") || index == 0) && + (contains(value, index + 2, 1, L_R_N_M_B_H_F_V_W_SPACE) || index + 1 == value.length() - 1))); + } + + /** + * Complex condition 0 for 'L' + */ + private boolean conditionL0(String value, int index) { + if (index == value.length() - 3 && + contains(value, index - 1, 4, "ILLO", "ILLA", "ALLE")) { + return true; + } else if ((contains(value, index - 1, 2, "AS", "OS") || + contains(value, value.length() - 1, 1, "A", "O")) && + contains(value, index - 1, 4, "ALLE")) { + return true; + } else { + return false; + } + } + + /** + * Complex condition 0 for 'M' + */ + private boolean conditionM0(String value, int index) { + if (charAt(value, index + 1) == 'M') { + return true; + } + return contains(value, index - 1, 3, "UMB") + && ((index + 1) == value.length() - 1 || contains(value, + index + 2, 2, "ER")); + } + + //-- BEGIN HELPER FUNCTIONS --// + + /** + * Determines whether or not a value is of slavo-germanic orgin. A value is + * of slavo-germanic origin if it contians any of 'W', 'K', 'CZ', or 'WITZ'. + */ + private boolean isSlavoGermanic(String value) { + return value.indexOf('W') > -1 || value.indexOf('K') > -1 || + value.indexOf("CZ") > -1 || value.indexOf("WITZ") > -1; + } + + /** + * Determines whether or not a character is a vowel or not + */ + private boolean isVowel(char ch) { + return VOWELS.indexOf(ch) != -1; + } + + /** + * Determines whether or not the value starts with a silent letter. It will + * return <code>true</code> if the value starts with any of 'GN', 'KN', + * 'PN', 'WR' or 'PS'. + */ + private boolean isSilentStart(String value) { + boolean result = false; + for (int i = 0; i < SILENT_START.length; i++) { + if (value.startsWith(SILENT_START[i])) { + result = true; + break; + } + } + return result; + } + + /** + * Cleans the input + */ + private String cleanInput(String input) { + if (input == null) { + return null; + } + input = input.trim(); + if (input.length() == 0) { + return null; + } + return input.toUpperCase(); + } + + /** + * Gets the character at index <code>index</code> if available, otherwise + * it returns <code>Character.MIN_VALUE</code> so that there is some sort + * of a default + */ + protected char charAt(String value, int index) { + if (index < 0 || index >= value.length()) { + return Character.MIN_VALUE; + } + return value.charAt(index); + } + + /** + * Shortcut method with 1 criteria + */ + private static boolean contains(String value, int start, int length, + String criteria) { + return contains(value, start, length, + new String[] { criteria }); + } + + /** + * Shortcut method with 2 criteria + */ + private static boolean contains(String value, int start, int length, + String criteria1, String criteria2) { + return contains(value, start, length, + new String[] { criteria1, criteria2 }); + } + + /** + * Shortcut method with 3 criteria + */ + private static boolean contains(String value, int start, int length, + String criteria1, String criteria2, + String criteria3) { + return contains(value, start, length, + new String[] { criteria1, criteria2, criteria3 }); + } + + /** + * Shortcut method with 4 criteria + */ + private static boolean contains(String value, int start, int length, + String criteria1, String criteria2, + String criteria3, String criteria4) { + return contains(value, start, length, + new String[] { criteria1, criteria2, criteria3, + criteria4 }); + } + + /** + * Shortcut method with 5 criteria + */ + private static boolean contains(String value, int start, int length, + String criteria1, String criteria2, + String criteria3, String criteria4, + String criteria5) { + return contains(value, start, length, + new String[] { criteria1, criteria2, criteria3, + criteria4, criteria5 }); + } + + /** + * Shortcut method with 6 criteria + */ + private static boolean contains(String value, int start, int length, + String criteria1, String criteria2, + String criteria3, String criteria4, + String criteria5, String criteria6) { + return contains(value, start, length, + new String[] { criteria1, criteria2, criteria3, + criteria4, criteria5, criteria6 }); + } + + /** + * Determines whether <code>value</code> contains any of the criteria + starting + * at index <code>start</code> and matching up to length <code>length</code> + */ + protected static boolean contains(String value, int start, int length, + String[] criteria) { + boolean result = false; + if (start >= 0 && start + length <= value.length()) { + String target = value.substring(start, start + length); + + for (int i = 0; i < criteria.length; i++) { + if (target.equals(criteria[i])) { + result = true; + break; + } + } + } + return result; + } + + //-- BEGIN INNER CLASSES --// + + /** + * Inner class for storing results, since there is the optional alternate + * encoding. + */ + public class DoubleMetaphoneResult { + + private StringBuffer primary = new StringBuffer(getMaxCodeLen()); + private StringBuffer alternate = new StringBuffer(getMaxCodeLen()); + private int maxLength; + + public DoubleMetaphoneResult(int maxLength) { + this.maxLength = maxLength; + } + + public void append(char value) { + appendPrimary(value); + appendAlternate(value); + } + + public void append(char primary, char alternate) { + appendPrimary(primary); + appendAlternate(alternate); + } + + public void appendPrimary(char value) { + if (this.primary.length() < this.maxLength) { + this.primary.append(value); + } + } + + public void appendAlternate(char value) { + if (this.alternate.length() < this.maxLength) { + this.alternate.append(value); + } + } + + public void append(String value) { + appendPrimary(value); + appendAlternate(value); + } + + public void append(String primary, String alternate) { + appendPrimary(primary); + appendAlternate(alternate); + } + + public void appendPrimary(String value) { + int addChars = this.maxLength - this.primary.length(); + if (value.length() <= addChars) { + this.primary.append(value); + } else { + this.primary.append(value.substring(0, addChars)); + } + } + + public void appendAlternate(String value) { + int addChars = this.maxLength - this.alternate.length(); + if (value.length() <= addChars) { + this.alternate.append(value); + } else { + this.alternate.append(value.substring(0, addChars)); + } + } + + public String getPrimary() { + return this.primary.toString(); + } + + public String getAlternate() { + return this.alternate.toString(); + } + + public boolean isComplete() { + return this.primary.length() >= this.maxLength && + this.alternate.length() >= this.maxLength; + } + } +} diff --git a/src/org/apache/commons/codec/language/Metaphone.java b/src/org/apache/commons/codec/language/Metaphone.java new file mode 100644 index 0000000..dce2c72 --- /dev/null +++ b/src/org/apache/commons/codec/language/Metaphone.java @@ -0,0 +1,399 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.language; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringEncoder; + +/** + * Encodes a string into a metaphone value. + * <p> + * Initial Java implementation by <CITE>William B. Brogden. December, 1997</CITE>. + * Permission given by <CITE>wbrogden</CITE> for code to be used anywhere. + * </p> + * <p> + * <CITE>Hanging on the Metaphone</CITE> by <CITE>Lawrence Philips</CITE> in <CITE>Computer Language of Dec. 1990, p + * 39.</CITE> + * </p> + * + * @author Apache Software Foundation + * @version $Id: Metaphone.java,v 1.20 2004/06/05 18:32:04 ggregory Exp $ + */ +public class Metaphone implements StringEncoder { + + /** + * Five values in the English language + */ + private String vowels = "AEIOU" ; + + /** + * Variable used in Metaphone algorithm + */ + private String frontv = "EIY" ; + + /** + * Variable used in Metaphone algorithm + */ + private String varson = "CSPTG" ; + + /** + * The max code length for metaphone is 4 + */ + private int maxCodeLen = 4 ; + + /** + * Creates an instance of the Metaphone encoder + */ + public Metaphone() { + super(); + } + + /** + * Find the metaphone value of a String. This is similar to the + * soundex algorithm, but better at finding similar sounding words. + * All input is converted to upper case. + * Limitations: Input format is expected to be a single ASCII word + * with only characters in the A - Z range, no punctuation or numbers. + * + * @param txt String to find the metaphone code for + * @return A metaphone code corresponding to the String supplied + */ + public String metaphone(String txt) { + boolean hard = false ; + if ((txt == null) || (txt.length() == 0)) { + return "" ; + } + // single character is itself + if (txt.length() == 1) { + return txt.toUpperCase() ; + } + + char[] inwd = txt.toUpperCase().toCharArray() ; + + StringBuffer local = new StringBuffer(40); // manipulate + StringBuffer code = new StringBuffer(10) ; // output + // handle initial 2 characters exceptions + switch(inwd[0]) { + case 'K' : + case 'G' : + case 'P' : /* looking for KN, etc*/ + if (inwd[1] == 'N') { + local.append(inwd, 1, inwd.length - 1); + } else { + local.append(inwd); + } + break; + case 'A': /* looking for AE */ + if (inwd[1] == 'E') { + local.append(inwd, 1, inwd.length - 1); + } else { + local.append(inwd); + } + break; + case 'W' : /* looking for WR or WH */ + if (inwd[1] == 'R') { // WR -> R + local.append(inwd, 1, inwd.length - 1); + break ; + } + if (inwd[1] == 'H') { + local.append(inwd, 1, inwd.length - 1); + local.setCharAt(0, 'W'); // WH -> W + } else { + local.append(inwd); + } + break; + case 'X' : /* initial X becomes S */ + inwd[0] = 'S'; + local.append(inwd); + break ; + default : + local.append(inwd); + } // now local has working string with initials fixed + + int wdsz = local.length(); + int n = 0 ; + + while ((code.length() < this.getMaxCodeLen()) && + (n < wdsz) ) { // max code size of 4 works well + char symb = local.charAt(n) ; + // remove duplicate letters except C + if ((symb != 'C') && (isPreviousChar( local, n, symb )) ) { + n++ ; + } else { // not dup + switch(symb) { + case 'A' : case 'E' : case 'I' : case 'O' : case 'U' : + if (n == 0) { + code.append(symb); + } + break ; // only use vowel if leading char + case 'B' : + if ( isPreviousChar(local, n, 'M') && + isLastChar(wdsz, n) ) { // B is silent if word ends in MB + break; + } + code.append(symb); + break; + case 'C' : // lots of C special cases + /* discard if SCI, SCE or SCY */ + if ( isPreviousChar(local, n, 'S') && + !isLastChar(wdsz, n) && + (this.frontv.indexOf(local.charAt(n + 1)) >= 0) ) { + break; + } + if (regionMatch(local, n, "CIA")) { // "CIA" -> X + code.append('X'); + break; + } + if (!isLastChar(wdsz, n) && + (this.frontv.indexOf(local.charAt(n + 1)) >= 0)) { + code.append('S'); + break; // CI,CE,CY -> S + } + if (isPreviousChar(local, n, 'S') && + isNextChar(local, n, 'H') ) { // SCH->sk + code.append('K') ; + break ; + } + if (isNextChar(local, n, 'H')) { // detect CH + if ((n == 0) && + (wdsz >= 3) && + isVowel(local,2) ) { // CH consonant -> K consonant + code.append('K'); + } else { + code.append('X'); // CHvowel -> X + } + } else { + code.append('K'); + } + break ; + case 'D' : + if (!isLastChar(wdsz, n + 1) && + isNextChar(local, n, 'G') && + (this.frontv.indexOf(local.charAt(n + 2)) >= 0)) { // DGE DGI DGY -> J + code.append('J'); n += 2 ; + } else { + code.append('T'); + } + break ; + case 'G' : // GH silent at end or before consonant + if (isLastChar(wdsz, n + 1) && + isNextChar(local, n, 'H')) { + break; + } + if (!isLastChar(wdsz, n + 1) && + isNextChar(local,n,'H') && + !isVowel(local,n+2)) { + break; + } + if ((n > 0) && + ( regionMatch(local, n, "GN") || + regionMatch(local, n, "GNED") ) ) { + break; // silent G + } + if (isPreviousChar(local, n, 'G')) { + hard = true ; + } else { + hard = false ; + } + if (!isLastChar(wdsz, n) && + (this.frontv.indexOf(local.charAt(n + 1)) >= 0) && + (!hard)) { + code.append('J'); + } else { + code.append('K'); + } + break ; + case 'H': + if (isLastChar(wdsz, n)) { + break ; // terminal H + } + if ((n > 0) && + (this.varson.indexOf(local.charAt(n - 1)) >= 0)) { + break; + } + if (isVowel(local,n+1)) { + code.append('H'); // Hvowel + } + break; + case 'F': + case 'J' : + case 'L' : + case 'M': + case 'N' : + case 'R' : + code.append(symb); + break; + case 'K' : + if (n > 0) { // not initial + if (!isPreviousChar(local, n, 'C')) { + code.append(symb); + } + } else { + code.append(symb); // initial K + } + break ; + case 'P' : + if (isNextChar(local,n,'H')) { + // PH -> F + code.append('F'); + } else { + code.append(symb); + } + break ; + case 'Q' : + code.append('K'); + break; + case 'S' : + if (regionMatch(local,n,"SH") || + regionMatch(local,n,"SIO") || + regionMatch(local,n,"SIA")) { + code.append('X'); + } else { + code.append('S'); + } + break; + case 'T' : + if (regionMatch(local,n,"TIA") || + regionMatch(local,n,"TIO")) { + code.append('X'); + break; + } + if (regionMatch(local,n,"TCH")) { + // Silent if in "TCH" + break; + } + // substitute numeral 0 for TH (resembles theta after all) + if (regionMatch(local,n,"TH")) { + code.append('0'); + } else { + code.append('T'); + } + break ; + case 'V' : + code.append('F'); break ; + case 'W' : case 'Y' : // silent if not followed by vowel + if (!isLastChar(wdsz,n) && + isVowel(local,n+1)) { + code.append(symb); + } + break ; + case 'X' : + code.append('K'); code.append('S'); + break ; + case 'Z' : + code.append('S'); break ; + } // end switch + n++ ; + } // end else from symb != 'C' + if (code.length() > this.getMaxCodeLen()) { + code.setLength(this.getMaxCodeLen()); + } + } + return code.toString(); + } + + private boolean isVowel(StringBuffer string, int index) { + return (this.vowels.indexOf(string.charAt(index)) >= 0); + } + + private boolean isPreviousChar(StringBuffer string, int index, char c) { + boolean matches = false; + if( index > 0 && + index < string.length() ) { + matches = string.charAt(index - 1) == c; + } + return matches; + } + + private boolean isNextChar(StringBuffer string, int index, char c) { + boolean matches = false; + if( index >= 0 && + index < string.length() - 1 ) { + matches = string.charAt(index + 1) == c; + } + return matches; + } + + private boolean regionMatch(StringBuffer string, int index, String test) { + boolean matches = false; + if( index >= 0 && + (index + test.length() - 1) < string.length() ) { + String substring = string.substring( index, index + test.length()); + matches = substring.equals( test ); + } + return matches; + } + + private boolean isLastChar(int wdsz, int n) { + return n + 1 == wdsz; + } + + + /** + * Encodes an Object using the metaphone algorithm. This method + * is provided in order to satisfy the requirements of the + * Encoder interface, and will throw an EncoderException if the + * supplied object is not of type java.lang.String. + * + * @param pObject Object to encode + * @return An object (or type java.lang.String) containing the + * metaphone code which corresponds to the String supplied. + * @throws EncoderException if the parameter supplied is not + * of type java.lang.String + */ + public Object encode(Object pObject) throws EncoderException { + if (!(pObject instanceof java.lang.String)) { + throw new EncoderException("Parameter supplied to Metaphone encode is not of type java.lang.String"); + } + return metaphone((String) pObject); + } + + /** + * Encodes a String using the Metaphone algorithm. + * + * @param pString String object to encode + * @return The metaphone code corresponding to the String supplied + */ + public String encode(String pString) { + return metaphone(pString); + } + + /** + * Tests is the metaphones of two strings are identical. + * + * @param str1 First of two strings to compare + * @param str2 Second of two strings to compare + * @return true if the metaphones of these strings are identical, + * false otherwise. + */ + public boolean isMetaphoneEqual(String str1, String str2) { + return metaphone(str1).equals(metaphone(str2)); + } + + /** + * Returns the maxCodeLen. + * @return int + */ + public int getMaxCodeLen() { return this.maxCodeLen; } + + /** + * Sets the maxCodeLen. + * @param maxCodeLen The maxCodeLen to set + */ + public void setMaxCodeLen(int maxCodeLen) { this.maxCodeLen = maxCodeLen; } + +} diff --git a/src/org/apache/commons/codec/language/RefinedSoundex.java b/src/org/apache/commons/codec/language/RefinedSoundex.java new file mode 100644 index 0000000..dbf60fe --- /dev/null +++ b/src/org/apache/commons/codec/language/RefinedSoundex.java @@ -0,0 +1,186 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.language; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringEncoder; + +/** + * Encodes a string into a Refined Soundex value. A refined soundex code is + * optimized for spell checking words. Soundex method originally developed by + * <CITE>Margaret Odell</CITE> and <CITE>Robert Russell</CITE>. + * + * @author Apache Software Foundation + * @version $Id: RefinedSoundex.java,v 1.21 2004/06/05 18:32:04 ggregory Exp $ + */ +public class RefinedSoundex implements StringEncoder { + + /** + * This static variable contains an instance of the RefinedSoundex using + * the US_ENGLISH mapping. + */ + public static final RefinedSoundex US_ENGLISH = new RefinedSoundex(); + + /** + * RefinedSoundex is *refined* for a number of reasons one being that the + * mappings have been altered. This implementation contains default + * mappings for US English. + */ + public static final char[] US_ENGLISH_MAPPING = "01360240043788015936020505".toCharArray(); + + /** + * Every letter of the alphabet is "mapped" to a numerical value. This char + * array holds the values to which each letter is mapped. This + * implementation contains a default map for US_ENGLISH + */ + private char[] soundexMapping; + + /** + * Creates an instance of the RefinedSoundex object using the default US + * English mapping. + */ + public RefinedSoundex() { + this(US_ENGLISH_MAPPING); + } + + /** + * Creates a refined soundex instance using a custom mapping. This + * constructor can be used to customize the mapping, and/or possibly + * provide an internationalized mapping for a non-Western character set. + * + * @param mapping + * Mapping array to use when finding the corresponding code for + * a given character + */ + public RefinedSoundex(char[] mapping) { + this.soundexMapping = mapping; + } + + // BEGIN android-note + // Removed @see reference to SoundexUtils below, since the class isn't + // public. + // END android-note + /** + * Returns the number of characters in the two encoded Strings that are the + * same. This return value ranges from 0 to the length of the shortest + * encoded String: 0 indicates little or no similarity, and 4 out of 4 (for + * example) indicates strong similarity or identical values. For refined + * Soundex, the return value can be greater than 4. + * + * @param s1 + * A String that will be encoded and compared. + * @param s2 + * A String that will be encoded and compared. + * @return The number of characters in the two encoded Strings that are the + * same from 0 to to the length of the shortest encoded String. + * + * @see <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp"> + * MS T-SQL DIFFERENCE</a> + * + * @throws EncoderException + * if an error occurs encoding one of the strings + * @since 1.3 + */ + public int difference(String s1, String s2) throws EncoderException { + return SoundexUtils.difference(this, s1, s2); + } + + /** + * Encodes an Object using the refined soundex algorithm. This method is + * provided in order to satisfy the requirements of the Encoder interface, + * and will throw an EncoderException if the supplied object is not of type + * java.lang.String. + * + * @param pObject + * Object to encode + * @return An object (or type java.lang.String) containing the refined + * soundex code which corresponds to the String supplied. + * @throws EncoderException + * if the parameter supplied is not of type java.lang.String + */ + public Object encode(Object pObject) throws EncoderException { + if (!(pObject instanceof java.lang.String)) { + throw new EncoderException("Parameter supplied to RefinedSoundex encode is not of type java.lang.String"); + } + return soundex((String) pObject); + } + + /** + * Encodes a String using the refined soundex algorithm. + * + * @param pString + * A String object to encode + * @return A Soundex code corresponding to the String supplied + */ + public String encode(String pString) { + return soundex(pString); + } + + /** + * Returns the mapping code for a given character. The mapping codes are + * maintained in an internal char array named soundexMapping, and the + * default values of these mappings are US English. + * + * @param c + * char to get mapping for + * @return A character (really a numeral) to return for the given char + */ + char getMappingCode(char c) { + if (!Character.isLetter(c)) { + return 0; + } + return this.soundexMapping[Character.toUpperCase(c) - 'A']; + } + + /** + * Retreives the Refined Soundex code for a given String object. + * + * @param str + * String to encode using the Refined Soundex algorithm + * @return A soundex code for the String supplied + */ + public String soundex(String str) { + if (str == null) { + return null; + } + str = SoundexUtils.clean(str); + if (str.length() == 0) { + return str; + } + + StringBuffer sBuf = new StringBuffer(); + sBuf.append(str.charAt(0)); + + char last, current; + last = '*'; + + for (int i = 0; i < str.length(); i++) { + + current = getMappingCode(str.charAt(i)); + if (current == last) { + continue; + } else if (current != 0) { + sBuf.append(current); + } + + last = current; + + } + + return sBuf.toString(); + } +} diff --git a/src/org/apache/commons/codec/language/Soundex.java b/src/org/apache/commons/codec/language/Soundex.java new file mode 100644 index 0000000..61ce440 --- /dev/null +++ b/src/org/apache/commons/codec/language/Soundex.java @@ -0,0 +1,274 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.language; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringEncoder; + +/** + * Encodes a string into a Soundex value. Soundex is an encoding used to relate similar names, but can also be used as a + * general purpose scheme to find word with similar phonemes. + * + * @author Apache Software Foundation + * @version $Id: Soundex.java,v 1.26 2004/07/07 23:15:24 ggregory Exp $ + */ +public class Soundex implements StringEncoder { + + /** + * An instance of Soundex using the US_ENGLISH_MAPPING mapping. + * + * @see #US_ENGLISH_MAPPING + */ + public static final Soundex US_ENGLISH = new Soundex(); + + /** + * This is a default mapping of the 26 letters used in US English. A value of <code>0</code> for a letter position + * means do not encode. + * <p> + * (This constant is provided as both an implementation convenience and to allow Javadoc to pick + * up the value for the constant values page.) + * </p> + * + * @see #US_ENGLISH_MAPPING + */ + public static final String US_ENGLISH_MAPPING_STRING = "01230120022455012623010202"; + + /** + * This is a default mapping of the 26 letters used in US English. A value of <code>0</code> for a letter position + * means do not encode. + * + * @see Soundex#Soundex(char[]) + */ + public static final char[] US_ENGLISH_MAPPING = US_ENGLISH_MAPPING_STRING.toCharArray(); + + // BEGIN android-note + // Removed @see reference to SoundexUtils below, since the class isn't + // public. + // END android-note + /** + * Encodes the Strings and returns the number of characters in the two encoded Strings that are the same. This + * return value ranges from 0 through 4: 0 indicates little or no similarity, and 4 indicates strong similarity or + * identical values. + * + * @param s1 + * A String that will be encoded and compared. + * @param s2 + * A String that will be encoded and compared. + * @return The number of characters in the two encoded Strings that are the same from 0 to 4. + * + * @see <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp"> MS + * T-SQL DIFFERENCE </a> + * + * @throws EncoderException + * if an error occurs encoding one of the strings + * @since 1.3 + */ + public int difference(String s1, String s2) throws EncoderException { + return SoundexUtils.difference(this, s1, s2); + } + + /** + * The maximum length of a Soundex code - Soundex codes are only four characters by definition. + * + * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0. + */ + private int maxLength = 4; + + /** + * Every letter of the alphabet is "mapped" to a numerical value. This char array holds the values to which each + * letter is mapped. This implementation contains a default map for US_ENGLISH + */ + private char[] soundexMapping; + + /** + * Creates an instance using US_ENGLISH_MAPPING + * + * @see Soundex#Soundex(char[]) + * @see Soundex#US_ENGLISH_MAPPING + */ + public Soundex() { + this(US_ENGLISH_MAPPING); + } + + /** + * Creates a soundex instance using the given mapping. This constructor can be used to provide an internationalized + * mapping for a non-Western character set. + * + * Every letter of the alphabet is "mapped" to a numerical value. This char array holds the values to which each + * letter is mapped. This implementation contains a default map for US_ENGLISH + * + * @param mapping + * Mapping array to use when finding the corresponding code for a given character + */ + public Soundex(char[] mapping) { + this.setSoundexMapping(mapping); + } + + /** + * Encodes an Object using the soundex algorithm. This method is provided in order to satisfy the requirements of + * the Encoder interface, and will throw an EncoderException if the supplied object is not of type java.lang.String. + * + * @param pObject + * Object to encode + * @return An object (or type java.lang.String) containing the soundex code which corresponds to the String + * supplied. + * @throws EncoderException + * if the parameter supplied is not of type java.lang.String + * @throws IllegalArgumentException + * if a character is not mapped + */ + public Object encode(Object pObject) throws EncoderException { + if (!(pObject instanceof String)) { + throw new EncoderException("Parameter supplied to Soundex encode is not of type java.lang.String"); + } + return soundex((String) pObject); + } + + /** + * Encodes a String using the soundex algorithm. + * + * @param pString + * A String object to encode + * @return A Soundex code corresponding to the String supplied + * @throws IllegalArgumentException + * if a character is not mapped + */ + public String encode(String pString) { + return soundex(pString); + } + + /** + * Used internally by the SoundEx algorithm. + * + * Consonants from the same code group separated by W or H are treated as one. + * + * @param str + * the cleaned working string to encode (in upper case). + * @param index + * the character position to encode + * @return Mapping code for a particular character + * @throws IllegalArgumentException + * if the character is not mapped + */ + private char getMappingCode(String str, int index) { + char mappedChar = this.map(str.charAt(index)); + // HW rule check + if (index > 1 && mappedChar != '0') { + char hwChar = str.charAt(index - 1); + if ('H' == hwChar || 'W' == hwChar) { + char preHWChar = str.charAt(index - 2); + char firstCode = this.map(preHWChar); + if (firstCode == mappedChar || 'H' == preHWChar || 'W' == preHWChar) { + return 0; + } + } + } + return mappedChar; + } + + /** + * Returns the maxLength. Standard Soundex + * + * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0. + * @return int + */ + public int getMaxLength() { + return this.maxLength; + } + + /** + * Returns the soundex mapping. + * + * @return soundexMapping. + */ + private char[] getSoundexMapping() { + return this.soundexMapping; + } + + /** + * Maps the given upper-case character to it's Soudex code. + * + * @param ch + * An upper-case character. + * @return A Soundex code. + * @throws IllegalArgumentException + * Thrown if <code>ch</code> is not mapped. + */ + private char map(char ch) { + int index = ch - 'A'; + if (index < 0 || index >= this.getSoundexMapping().length) { + throw new IllegalArgumentException("The character is not mapped: " + ch); + } + return this.getSoundexMapping()[index]; + } + + /** + * Sets the maxLength. + * + * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0. + * @param maxLength + * The maxLength to set + */ + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + /** + * Sets the soundexMapping. + * + * @param soundexMapping + * The soundexMapping to set. + */ + private void setSoundexMapping(char[] soundexMapping) { + this.soundexMapping = soundexMapping; + } + + /** + * Retreives the Soundex code for a given String object. + * + * @param str + * String to encode using the Soundex algorithm + * @return A soundex code for the String supplied + * @throws IllegalArgumentException + * if a character is not mapped + */ + public String soundex(String str) { + if (str == null) { + return null; + } + str = SoundexUtils.clean(str); + if (str.length() == 0) { + return str; + } + char out[] = {'0', '0', '0', '0'}; + char last, mapped; + int incount = 1, count = 1; + out[0] = str.charAt(0); + last = getMappingCode(str, 0); + while ((incount < str.length()) && (count < out.length)) { + mapped = getMappingCode(str, incount++); + if (mapped != 0) { + if ((mapped != '0') && (mapped != last)) { + out[count++] = mapped; + } + last = mapped; + } + } + return new String(out); + } + +} diff --git a/src/org/apache/commons/codec/language/SoundexUtils.java b/src/org/apache/commons/codec/language/SoundexUtils.java new file mode 100644 index 0000000..48f2d87 --- /dev/null +++ b/src/org/apache/commons/codec/language/SoundexUtils.java @@ -0,0 +1,122 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.language; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringEncoder; + +/** + * Utility methods for {@link Soundex} and {@link RefinedSoundex} classes. + * + * @author Apache Software Foundation + * @version $Id: SoundexUtils.java,v 1.5 2004/03/17 18:31:35 ggregory Exp $ + * @since 1.3 + */ +final class SoundexUtils { + + /** + * Cleans up the input string before Soundex processing by only returning + * upper case letters. + * + * @param str + * The String to clean. + * @return A clean String. + */ + static String clean(String str) { + if (str == null || str.length() == 0) { + return str; + } + int len = str.length(); + char[] chars = new char[len]; + int count = 0; + for (int i = 0; i < len; i++) { + if (Character.isLetter(str.charAt(i))) { + chars[count++] = str.charAt(i); + } + } + if (count == len) { + return str.toUpperCase(); + } + return new String(chars, 0, count).toUpperCase(); + } + + /** + * Encodes the Strings and returns the number of characters in the two + * encoded Strings that are the same. + * <ul> + * <li>For Soundex, this return value ranges from 0 through 4: 0 indicates + * little or no similarity, and 4 indicates strong similarity or identical + * values.</li> + * <li>For refined Soundex, the return value can be greater than 4.</li> + * </ul> + * + * @param encoder + * The encoder to use to encode the Strings. + * @param s1 + * A String that will be encoded and compared. + * @param s2 + * A String that will be encoded and compared. + * @return The number of characters in the two Soundex encoded Strings that + * are the same. + * + * @see #differenceEncoded(String,String) + * @see <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp"> + * MS T-SQL DIFFERENCE</a> + * + * @throws EncoderException + * if an error occurs encoding one of the strings + */ + static int difference(StringEncoder encoder, String s1, String s2) throws EncoderException { + return differenceEncoded(encoder.encode(s1), encoder.encode(s2)); + } + + /** + * Returns the number of characters in the two Soundex encoded Strings that + * are the same. + * <ul> + * <li>For Soundex, this return value ranges from 0 through 4: 0 indicates + * little or no similarity, and 4 indicates strong similarity or identical + * values.</li> + * <li>For refined Soundex, the return value can be greater than 4.</li> + * </ul> + * + * @param es1 + * An encoded String. + * @param es2 + * An encoded String. + * @return The number of characters in the two Soundex encoded Strings that + * are the same. + * + * @see <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp"> + * MS T-SQL DIFFERENCE</a> + */ + static int differenceEncoded(String es1, String es2) { + + if (es1 == null || es2 == null) { + return 0; + } + int lengthToMatch = Math.min(es1.length(), es2.length()); + int diff = 0; + for (int i = 0; i < lengthToMatch; i++) { + if (es1.charAt(i) == es2.charAt(i)) { + diff++; + } + } + return diff; + } + +} diff --git a/src/org/apache/commons/codec/language/package.html b/src/org/apache/commons/codec/language/package.html new file mode 100644 index 0000000..fab8e4c --- /dev/null +++ b/src/org/apache/commons/codec/language/package.html @@ -0,0 +1,20 @@ +<!-- +Copyright 2003-2004 The Apache Software Foundation. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<html> + <body> + Language and phonetic encoders. + </body> +</html> diff --git a/src/org/apache/commons/codec/net/BCodec.java b/src/org/apache/commons/codec/net/BCodec.java new file mode 100644 index 0000000..b164100 --- /dev/null +++ b/src/org/apache/commons/codec/net/BCodec.java @@ -0,0 +1,207 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.net; + +import java.io.UnsupportedEncodingException; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringDecoder; +import org.apache.commons.codec.StringEncoder; +import org.apache.commons.codec.binary.Base64; + +/** + * <p> + * Identical to the Base64 encoding defined by <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC + * 1521</a> and allows a character set to be specified. + * </p> + * + * <p> + * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII + * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message + * handling software. + * </p> + * + * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message + * Header Extensions for Non-ASCII Text</a> + * + * @author Apache Software Foundation + * @since 1.3 + * @version $Id: BCodec.java,v 1.5 2004/04/13 22:46:37 ggregory Exp $ + */ +public class BCodec extends RFC1522Codec implements StringEncoder, StringDecoder { + /** + * The default charset used for string decoding and encoding. + */ + private String charset = StringEncodings.UTF8; + + /** + * Default constructor. + */ + public BCodec() { + super(); + } + + /** + * Constructor which allows for the selection of a default charset + * + * @param charset + * the default string charset to use. + * + * @see <a href="http://java.sun.com/j2se/1.3/docs/api/java/lang/package-summary.html#charenc">JRE character + * encoding names</a> + */ + public BCodec(final String charset) { + super(); + this.charset = charset; + } + + protected String getEncoding() { + return "B"; + } + + protected byte[] doEncoding(byte[] bytes) throws EncoderException { + if (bytes == null) { + return null; + } + return Base64.encodeBase64(bytes); + } + + protected byte[] doDecoding(byte[] bytes) throws DecoderException { + if (bytes == null) { + return null; + } + return Base64.decodeBase64(bytes); + } + + /** + * Encodes a string into its Base64 form using the specified charset. Unsafe characters are escaped. + * + * @param value + * string to convert to Base64 form + * @param charset + * the charset for pString + * @return Base64 string + * + * @throws EncoderException + * thrown if a failure condition is encountered during the encoding process. + */ + public String encode(final String value, final String charset) throws EncoderException { + if (value == null) { + return null; + } + try { + return encodeText(value, charset); + } catch (UnsupportedEncodingException e) { + throw new EncoderException(e.getMessage()); + } + } + + /** + * Encodes a string into its Base64 form using the default charset. Unsafe characters are escaped. + * + * @param value + * string to convert to Base64 form + * @return Base64 string + * + * @throws EncoderException + * thrown if a failure condition is encountered during the encoding process. + */ + public String encode(String value) throws EncoderException { + if (value == null) { + return null; + } + return encode(value, getDefaultCharset()); + } + + /** + * Decodes a Base64 string into its original form. Escaped characters are converted back to their original + * representation. + * + * @param value + * Base64 string to convert into its original form + * + * @return original string + * + * @throws DecoderException + * A decoder exception is thrown if a failure condition is encountered during the decode process. + */ + public String decode(String value) throws DecoderException { + if (value == null) { + return null; + } + try { + return decodeText(value); + } catch (UnsupportedEncodingException e) { + throw new DecoderException(e.getMessage()); + } + } + + /** + * Encodes an object into its Base64 form using the default charset. Unsafe characters are escaped. + * + * @param value + * object to convert to Base64 form + * @return Base64 object + * + * @throws EncoderException + * thrown if a failure condition is encountered during the encoding process. + */ + public Object encode(Object value) throws EncoderException { + if (value == null) { + return null; + } else if (value instanceof String) { + return encode((String) value); + } else { + throw new EncoderException("Objects of type " + + value.getClass().getName() + + " cannot be encoded using BCodec"); + } + } + + /** + * Decodes a Base64 object into its original form. Escaped characters are converted back to their original + * representation. + * + * @param value + * Base64 object to convert into its original form + * + * @return original object + * + * @throws DecoderException + * A decoder exception is thrown if a failure condition is encountered during the decode process. + */ + public Object decode(Object value) throws DecoderException { + if (value == null) { + return null; + } else if (value instanceof String) { + return decode((String) value); + } else { + throw new DecoderException("Objects of type " + + value.getClass().getName() + + " cannot be decoded using BCodec"); + } + } + + /** + * The default charset used for string decoding and encoding. + * + * @return the default string charset. + */ + public String getDefaultCharset() { + return this.charset; + } +} diff --git a/src/org/apache/commons/codec/net/QCodec.java b/src/org/apache/commons/codec/net/QCodec.java new file mode 100644 index 0000000..5736080 --- /dev/null +++ b/src/org/apache/commons/codec/net/QCodec.java @@ -0,0 +1,309 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.net; + +import java.io.UnsupportedEncodingException; +import java.util.BitSet; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringDecoder; +import org.apache.commons.codec.StringEncoder; + +/** + * <p> + * Similar to the Quoted-Printable content-transfer-encoding defined in <a + * href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a> and designed to allow text containing mostly ASCII + * characters to be decipherable on an ASCII terminal without decoding. + * </p> + * + * <p> + * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII + * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message + * handling software. + * </p> + * + * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message + * Header Extensions for Non-ASCII Text</a> + * + * @author Apache Software Foundation + * @since 1.3 + * @version $Id: QCodec.java,v 1.6 2004/05/24 00:24:32 ggregory Exp $ + */ +public class QCodec extends RFC1522Codec implements StringEncoder, StringDecoder { + /** + * The default charset used for string decoding and encoding. + */ + private String charset = StringEncodings.UTF8; + + /** + * BitSet of printable characters as defined in RFC 1522. + */ + private static final BitSet PRINTABLE_CHARS = new BitSet(256); + // Static initializer for printable chars collection + static { + // alpha characters + PRINTABLE_CHARS.set(' '); + PRINTABLE_CHARS.set('!'); + PRINTABLE_CHARS.set('"'); + PRINTABLE_CHARS.set('#'); + PRINTABLE_CHARS.set('$'); + PRINTABLE_CHARS.set('%'); + PRINTABLE_CHARS.set('&'); + PRINTABLE_CHARS.set('\''); + PRINTABLE_CHARS.set('('); + PRINTABLE_CHARS.set(')'); + PRINTABLE_CHARS.set('*'); + PRINTABLE_CHARS.set('+'); + PRINTABLE_CHARS.set(','); + PRINTABLE_CHARS.set('-'); + PRINTABLE_CHARS.set('.'); + PRINTABLE_CHARS.set('/'); + for (int i = '0'; i <= '9'; i++) { + PRINTABLE_CHARS.set(i); + } + PRINTABLE_CHARS.set(':'); + PRINTABLE_CHARS.set(';'); + PRINTABLE_CHARS.set('<'); + PRINTABLE_CHARS.set('>'); + PRINTABLE_CHARS.set('@'); + for (int i = 'A'; i <= 'Z'; i++) { + PRINTABLE_CHARS.set(i); + } + PRINTABLE_CHARS.set('['); + PRINTABLE_CHARS.set('\\'); + PRINTABLE_CHARS.set(']'); + PRINTABLE_CHARS.set('^'); + PRINTABLE_CHARS.set('`'); + for (int i = 'a'; i <= 'z'; i++) { + PRINTABLE_CHARS.set(i); + } + PRINTABLE_CHARS.set('{'); + PRINTABLE_CHARS.set('|'); + PRINTABLE_CHARS.set('}'); + PRINTABLE_CHARS.set('~'); + } + + private static byte BLANK = 32; + + private static byte UNDERSCORE = 95; + + private boolean encodeBlanks = false; + + /** + * Default constructor. + */ + public QCodec() { + super(); + } + + /** + * Constructor which allows for the selection of a default charset + * + * @param charset + * the default string charset to use. + * + * @see <a href="http://java.sun.com/j2se/1.3/docs/api/java/lang/package-summary.html#charenc">JRE character + * encoding names</a> + */ + public QCodec(final String charset) { + super(); + this.charset = charset; + } + + protected String getEncoding() { + return "Q"; + } + + protected byte[] doEncoding(byte[] bytes) throws EncoderException { + if (bytes == null) { + return null; + } + byte[] data = QuotedPrintableCodec.encodeQuotedPrintable(PRINTABLE_CHARS, bytes); + if (this.encodeBlanks) { + for (int i = 0; i < data.length; i++) { + if (data[i] == BLANK) { + data[i] = UNDERSCORE; + } + } + } + return data; + } + + protected byte[] doDecoding(byte[] bytes) throws DecoderException { + if (bytes == null) { + return null; + } + boolean hasUnderscores = false; + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] == UNDERSCORE) { + hasUnderscores = true; + break; + } + } + if (hasUnderscores) { + byte[] tmp = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + if (b != UNDERSCORE) { + tmp[i] = b; + } else { + tmp[i] = BLANK; + } + } + return QuotedPrintableCodec.decodeQuotedPrintable(tmp); + } + return QuotedPrintableCodec.decodeQuotedPrintable(bytes); + } + + /** + * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped. + * + * @param pString + * string to convert to quoted-printable form + * @param charset + * the charset for pString + * @return quoted-printable string + * + * @throws EncoderException + * thrown if a failure condition is encountered during the encoding process. + */ + public String encode(final String pString, final String charset) throws EncoderException { + if (pString == null) { + return null; + } + try { + return encodeText(pString, charset); + } catch (UnsupportedEncodingException e) { + throw new EncoderException(e.getMessage()); + } + } + + /** + * Encodes a string into its quoted-printable form using the default charset. Unsafe characters are escaped. + * + * @param pString + * string to convert to quoted-printable form + * @return quoted-printable string + * + * @throws EncoderException + * thrown if a failure condition is encountered during the encoding process. + */ + public String encode(String pString) throws EncoderException { + if (pString == null) { + return null; + } + return encode(pString, getDefaultCharset()); + } + + /** + * Decodes a quoted-printable string into its original form. Escaped characters are converted back to their original + * representation. + * + * @param pString + * quoted-printable string to convert into its original form + * + * @return original string + * + * @throws DecoderException + * A decoder exception is thrown if a failure condition is encountered during the decode process. + */ + public String decode(String pString) throws DecoderException { + if (pString == null) { + return null; + } + try { + return decodeText(pString); + } catch (UnsupportedEncodingException e) { + throw new DecoderException(e.getMessage()); + } + } + + /** + * Encodes an object into its quoted-printable form using the default charset. Unsafe characters are escaped. + * + * @param pObject + * object to convert to quoted-printable form + * @return quoted-printable object + * + * @throws EncoderException + * thrown if a failure condition is encountered during the encoding process. + */ + public Object encode(Object pObject) throws EncoderException { + if (pObject == null) { + return null; + } else if (pObject instanceof String) { + return encode((String) pObject); + } else { + throw new EncoderException("Objects of type " + + pObject.getClass().getName() + + " cannot be encoded using Q codec"); + } + } + + /** + * Decodes a quoted-printable object into its original form. Escaped characters are converted back to their original + * representation. + * + * @param pObject + * quoted-printable object to convert into its original form + * + * @return original object + * + * @throws DecoderException + * A decoder exception is thrown if a failure condition is encountered during the decode process. + */ + public Object decode(Object pObject) throws DecoderException { + if (pObject == null) { + return null; + } else if (pObject instanceof String) { + return decode((String) pObject); + } else { + throw new DecoderException("Objects of type " + + pObject.getClass().getName() + + " cannot be decoded using Q codec"); + } + } + + /** + * The default charset used for string decoding and encoding. + * + * @return the default string charset. + */ + public String getDefaultCharset() { + return this.charset; + } + + /** + * Tests if optional tranformation of SPACE characters is to be used + * + * @return <code>true</code> if SPACE characters are to be transformed, <code>false</code> otherwise + */ + public boolean isEncodeBlanks() { + return this.encodeBlanks; + } + + /** + * Defines whether optional tranformation of SPACE characters is to be used + * + * @param b + * <code>true</code> if SPACE characters are to be transformed, <code>false</code> otherwise + */ + public void setEncodeBlanks(boolean b) { + this.encodeBlanks = b; + } +} diff --git a/src/org/apache/commons/codec/net/QuotedPrintableCodec.java b/src/org/apache/commons/codec/net/QuotedPrintableCodec.java new file mode 100644 index 0000000..c2fcd27 --- /dev/null +++ b/src/org/apache/commons/codec/net/QuotedPrintableCodec.java @@ -0,0 +1,387 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.net; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.util.BitSet; +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringDecoder; +import org.apache.commons.codec.StringEncoder; + +/** + * <p> + * Codec for the Quoted-Printable section of <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521 </a>. + * </p> + * <p> + * The Quoted-Printable encoding is intended to represent data that largely consists of octets that correspond to + * printable characters in the ASCII character set. It encodes the data in such a way that the resulting octets are + * unlikely to be modified by mail transport. If the data being encoded are mostly ASCII text, the encoded form of the + * data remains largely recognizable by humans. A body which is entirely ASCII may also be encoded in Quoted-Printable + * to ensure the integrity of the data should the message pass through a character- translating, and/or line-wrapping + * gateway. + * </p> + * + * <p> + * Note: + * </p> + * <p> + * Rules #3, #4, and #5 of the quoted-printable spec are not implemented yet because the complete quoted-printable spec + * does not lend itself well into the byte[] oriented codec framework. Complete the codec once the steamable codec + * framework is ready. The motivation behind providing the codec in a partial form is that it can already come in handy + * for those applications that do not require quoted-printable line formatting (rules #3, #4, #5), for instance Q codec. + * </p> + * + * @see <a href="http://www.ietf.org/rfc/rfc1521.txt"> RFC 1521 MIME (Multipurpose Internet Mail Extensions) Part One: + * Mechanisms for Specifying and Describing the Format of Internet Message Bodies </a> + * + * @author Apache Software Foundation + * @since 1.3 + * @version $Id: QuotedPrintableCodec.java,v 1.7 2004/04/09 22:21:07 ggregory Exp $ + */ +public class QuotedPrintableCodec implements BinaryEncoder, BinaryDecoder, StringEncoder, StringDecoder { + /** + * The default charset used for string decoding and encoding. + */ + private String charset = StringEncodings.UTF8; + + /** + * BitSet of printable characters as defined in RFC 1521. + */ + private static final BitSet PRINTABLE_CHARS = new BitSet(256); + + private static byte ESCAPE_CHAR = '='; + + private static byte TAB = 9; + + private static byte SPACE = 32; + // Static initializer for printable chars collection + static { + // alpha characters + for (int i = 33; i <= 60; i++) { + PRINTABLE_CHARS.set(i); + } + for (int i = 62; i <= 126; i++) { + PRINTABLE_CHARS.set(i); + } + PRINTABLE_CHARS.set(TAB); + PRINTABLE_CHARS.set(SPACE); + } + + /** + * Default constructor. + */ + public QuotedPrintableCodec() { + super(); + } + + /** + * Constructor which allows for the selection of a default charset + * + * @param charset + * the default string charset to use. + */ + public QuotedPrintableCodec(String charset) { + super(); + this.charset = charset; + } + + /** + * Encodes byte into its quoted-printable representation. + * + * @param b + * byte to encode + * @param buffer + * the buffer to write to + */ + private static final void encodeQuotedPrintable(int b, ByteArrayOutputStream buffer) { + buffer.write(ESCAPE_CHAR); + char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); + char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); + buffer.write(hex1); + buffer.write(hex2); + } + + /** + * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped. + * + * <p> + * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in + * RFC 1521 and is suitable for encoding binary data and unformatted text. + * </p> + * + * @param printable + * bitset of characters deemed quoted-printable + * @param bytes + * array of bytes to be encoded + * @return array of bytes containing quoted-printable data + */ + public static final byte[] encodeQuotedPrintable(BitSet printable, byte[] bytes) { + if (bytes == null) { + return null; + } + if (printable == null) { + printable = PRINTABLE_CHARS; + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + if (b < 0) { + b = 256 + b; + } + if (printable.get(b)) { + buffer.write(b); + } else { + encodeQuotedPrintable(b, buffer); + } + } + return buffer.toByteArray(); + } + + /** + * Decodes an array quoted-printable characters into an array of original bytes. Escaped characters are converted + * back to their original representation. + * + * <p> + * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in + * RFC 1521. + * </p> + * + * @param bytes + * array of quoted-printable characters + * @return array of original bytes + * @throws DecoderException + * Thrown if quoted-printable decoding is unsuccessful + */ + public static final byte[] decodeQuotedPrintable(byte[] bytes) throws DecoderException { + if (bytes == null) { + return null; + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + if (b == ESCAPE_CHAR) { + try { + int u = Character.digit((char) bytes[++i], 16); + int l = Character.digit((char) bytes[++i], 16); + if (u == -1 || l == -1) { + throw new DecoderException("Invalid quoted-printable encoding"); + } + buffer.write((char) ((u << 4) + l)); + } catch (ArrayIndexOutOfBoundsException e) { + throw new DecoderException("Invalid quoted-printable encoding"); + } + } else { + buffer.write(b); + } + } + return buffer.toByteArray(); + } + + /** + * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped. + * + * <p> + * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in + * RFC 1521 and is suitable for encoding binary data and unformatted text. + * </p> + * + * @param bytes + * array of bytes to be encoded + * @return array of bytes containing quoted-printable data + */ + public byte[] encode(byte[] bytes) { + return encodeQuotedPrintable(PRINTABLE_CHARS, bytes); + } + + /** + * Decodes an array of quoted-printable characters into an array of original bytes. Escaped characters are converted + * back to their original representation. + * + * <p> + * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in + * RFC 1521. + * </p> + * + * @param bytes + * array of quoted-printable characters + * @return array of original bytes + * @throws DecoderException + * Thrown if quoted-printable decoding is unsuccessful + */ + public byte[] decode(byte[] bytes) throws DecoderException { + return decodeQuotedPrintable(bytes); + } + + /** + * Encodes a string into its quoted-printable form using the default string charset. Unsafe characters are escaped. + * + * <p> + * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in + * RFC 1521 and is suitable for encoding binary data. + * </p> + * + * @param pString + * string to convert to quoted-printable form + * @return quoted-printable string + * + * @throws EncoderException + * Thrown if quoted-printable encoding is unsuccessful + * + * @see #getDefaultCharset() + */ + public String encode(String pString) throws EncoderException { + if (pString == null) { + return null; + } + try { + return encode(pString, getDefaultCharset()); + } catch (UnsupportedEncodingException e) { + throw new EncoderException(e.getMessage()); + } + } + + /** + * Decodes a quoted-printable string into its original form using the specified string charset. Escaped characters + * are converted back to their original representation. + * + * @param pString + * quoted-printable string to convert into its original form + * @param charset + * the original string charset + * @return original string + * @throws DecoderException + * Thrown if quoted-printable decoding is unsuccessful + * @throws UnsupportedEncodingException + * Thrown if charset is not supported + */ + public String decode(String pString, String charset) throws DecoderException, UnsupportedEncodingException { + if (pString == null) { + return null; + } + return new String(decode(pString.getBytes(StringEncodings.US_ASCII)), charset); + } + + /** + * Decodes a quoted-printable string into its original form using the default string charset. Escaped characters are + * converted back to their original representation. + * + * @param pString + * quoted-printable string to convert into its original form + * @return original string + * @throws DecoderException + * Thrown if quoted-printable decoding is unsuccessful + * @throws UnsupportedEncodingException + * Thrown if charset is not supported + * @see #getDefaultCharset() + */ + public String decode(String pString) throws DecoderException { + if (pString == null) { + return null; + } + try { + return decode(pString, getDefaultCharset()); + } catch (UnsupportedEncodingException e) { + throw new DecoderException(e.getMessage()); + } + } + + /** + * Encodes an object into its quoted-printable safe form. Unsafe characters are escaped. + * + * @param pObject + * string to convert to a quoted-printable form + * @return quoted-printable object + * @throws EncoderException + * Thrown if quoted-printable encoding is not applicable to objects of this type or if encoding is + * unsuccessful + */ + public Object encode(Object pObject) throws EncoderException { + if (pObject == null) { + return null; + } else if (pObject instanceof byte[]) { + return encode((byte[]) pObject); + } else if (pObject instanceof String) { + return encode((String) pObject); + } else { + throw new EncoderException("Objects of type " + + pObject.getClass().getName() + + " cannot be quoted-printable encoded"); + } + } + + /** + * Decodes a quoted-printable object into its original form. Escaped characters are converted back to their original + * representation. + * + * @param pObject + * quoted-printable object to convert into its original form + * @return original object + * @throws DecoderException + * Thrown if quoted-printable decoding is not applicable to objects of this type if decoding is + * unsuccessful + */ + public Object decode(Object pObject) throws DecoderException { + if (pObject == null) { + return null; + } else if (pObject instanceof byte[]) { + return decode((byte[]) pObject); + } else if (pObject instanceof String) { + return decode((String) pObject); + } else { + throw new DecoderException("Objects of type " + + pObject.getClass().getName() + + " cannot be quoted-printable decoded"); + } + } + + /** + * Returns the default charset used for string decoding and encoding. + * + * @return the default string charset. + */ + public String getDefaultCharset() { + return this.charset; + } + + /** + * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped. + * + * <p> + * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in + * RFC 1521 and is suitable for encoding binary data and unformatted text. + * </p> + * + * @param pString + * string to convert to quoted-printable form + * @param charset + * the charset for pString + * @return quoted-printable string + * + * @throws UnsupportedEncodingException + * Thrown if the charset is not supported + */ + public String encode(String pString, String charset) throws UnsupportedEncodingException { + if (pString == null) { + return null; + } + return new String(encode(pString.getBytes(charset)), StringEncodings.US_ASCII); + } +} diff --git a/src/org/apache/commons/codec/net/RFC1522Codec.java b/src/org/apache/commons/codec/net/RFC1522Codec.java new file mode 100644 index 0000000..0acf921 --- /dev/null +++ b/src/org/apache/commons/codec/net/RFC1522Codec.java @@ -0,0 +1,161 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.net; + +import java.io.UnsupportedEncodingException; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +/** + * <p> + * Implements methods common to all codecs defined in RFC 1522. + * </p> + * + * <p> + * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> + * describes techniques to allow the encoding of non-ASCII text in + * various portions of a RFC 822 [2] message header, in a manner which + * is unlikely to confuse existing message handling software. + * </p> + + * @see <a href="http://www.ietf.org/rfc/rfc1522.txt"> + * MIME (Multipurpose Internet Mail Extensions) Part Two: + * Message Header Extensions for Non-ASCII Text</a> + * </p> + * + * @author Apache Software Foundation + * @since 1.3 + * @version $Id: RFC1522Codec.java,v 1.2 2004/04/09 22:21:43 ggregory Exp $ + */ +abstract class RFC1522Codec { + + /** + * Applies an RFC 1522 compliant encoding scheme to the given string of text with the + * given charset. This method constructs the "encoded-word" header common to all the + * RFC 1522 codecs and then invokes {@link #doEncoding(byte [])} method of a concrete + * class to perform the specific enconding. + * + * @param text a string to encode + * @param charset a charset to be used + * + * @return RFC 1522 compliant "encoded-word" + * + * @throws EncoderException thrown if there is an error conidition during the Encoding + * process. + * @throws UnsupportedEncodingException thrown if charset is not supported + * + * @see <a href="http://java.sun.com/j2se/1.3/docs/api/java/lang/package-summary.html#charenc">JRE character + * encoding names</a> + */ + protected String encodeText(final String text, final String charset) + throws EncoderException, UnsupportedEncodingException + { + if (text == null) { + return null; + } + StringBuffer buffer = new StringBuffer(); + buffer.append("=?"); + buffer.append(charset); + buffer.append('?'); + buffer.append(getEncoding()); + buffer.append('?'); + byte [] rawdata = doEncoding(text.getBytes(charset)); + buffer.append(new String(rawdata, StringEncodings.US_ASCII)); + buffer.append("?="); + return buffer.toString(); + } + + /** + * Applies an RFC 1522 compliant decoding scheme to the given string of text. This method + * processes the "encoded-word" header common to all the RFC 1522 codecs and then invokes + * {@link #doEncoding(byte [])} method of a concrete class to perform the specific deconding. + * + * @param text a string to decode + * + * @throws DecoderException thrown if there is an error conidition during the Decoding + * process. + * @throws UnsupportedEncodingException thrown if charset specified in the "encoded-word" + * header is not supported + */ + protected String decodeText(final String text) + throws DecoderException, UnsupportedEncodingException + { + if (text == null) { + return null; + } + if ((!text.startsWith("=?")) || (!text.endsWith("?="))) { + throw new DecoderException("RFC 1522 violation: malformed encoded content"); + } + int termnator = text.length() - 2; + int from = 2; + int to = text.indexOf("?", from); + if ((to == -1) || (to == termnator)) { + throw new DecoderException("RFC 1522 violation: charset token not found"); + } + String charset = text.substring(from, to); + if (charset.equals("")) { + throw new DecoderException("RFC 1522 violation: charset not specified"); + } + from = to + 1; + to = text.indexOf("?", from); + if ((to == -1) || (to == termnator)) { + throw new DecoderException("RFC 1522 violation: encoding token not found"); + } + String encoding = text.substring(from, to); + if (!getEncoding().equalsIgnoreCase(encoding)) { + throw new DecoderException("This codec cannot decode " + + encoding + " encoded content"); + } + from = to + 1; + to = text.indexOf("?", from); + byte[] data = text.substring(from, to).getBytes(StringEncodings.US_ASCII); + data = doDecoding(data); + return new String(data, charset); + } + + /** + * Returns the codec name (referred to as encoding in the RFC 1522) + * + * @return name of the codec + */ + protected abstract String getEncoding(); + + /** + * Encodes an array of bytes using the defined encoding scheme + * + * @param bytes Data to be encoded + * + * @return A byte array containing the encoded data + * + * @throws EncoderException thrown if the Encoder encounters a failure condition + * during the encoding process. + */ + protected abstract byte[] doEncoding(byte[] bytes) throws EncoderException; + + /** + * Decodes an array of bytes using the defined encoding scheme + * + * @param bytes Data to be decoded + * + * @return a byte array that contains decoded data + * + * @throws DecoderException A decoder exception is thrown if a Decoder encounters a + * failure condition during the decode process. + */ + protected abstract byte[] doDecoding(byte[] bytes) throws DecoderException; +} diff --git a/src/org/apache/commons/codec/net/StringEncodings.java b/src/org/apache/commons/codec/net/StringEncodings.java new file mode 100644 index 0000000..e7f6bb8 --- /dev/null +++ b/src/org/apache/commons/codec/net/StringEncodings.java @@ -0,0 +1,52 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.net; + +/** + * String encodings used in this package. + * + * @author Apache Software Foundation + * @since 1.3 + * @version $Id: StringEncodings.java,v 1.2 2004/04/09 22:21:07 ggregory Exp $ + */ +interface StringEncodings { + /** + * <p> + * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. + * </p> + * <p> + * Every implementation of the Java platform is required to support this character encoding. + * </p> + * + * @see <a href="http://java.sun.com/j2se/1.3/docs/api/java/lang/package-summary.html#charenc">JRE character + * encoding names</a> + */ + String US_ASCII = "US-ASCII"; + + /** + * <p> + * Eight-bit Unicode Transformation Format. + * </p> + * <p> + * Every implementation of the Java platform is required to support this character encoding. + * </p> + * + * @see <a href="http://java.sun.com/j2se/1.3/docs/api/java/lang/package-summary.html#charenc">JRE character + * encoding names</a> + */ + String UTF8 = "UTF-8"; +} diff --git a/src/org/apache/commons/codec/net/URLCodec.java b/src/org/apache/commons/codec/net/URLCodec.java new file mode 100644 index 0000000..1bc3507 --- /dev/null +++ b/src/org/apache/commons/codec/net/URLCodec.java @@ -0,0 +1,364 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.codec.net; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.util.BitSet; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringDecoder; +import org.apache.commons.codec.StringEncoder; + +/** + * <p>Implements the 'www-form-urlencoded' encoding scheme, + * also misleadingly known as URL encoding.</p> + * + * <p>For more detailed information please refer to + * <a href="http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1"> + * Chapter 17.13.4 'Form content types'</a> of the + * <a href="http://www.w3.org/TR/html4/">HTML 4.01 Specification<a></p> + * + * <p> + * This codec is meant to be a replacement for standard Java classes + * {@link java.net.URLEncoder} and {@link java.net.URLDecoder} + * on older Java platforms, as these classes in Java versions below + * 1.4 rely on the platform's default charset encoding. + * </p> + * + * @author Apache Software Foundation + * @since 1.2 + * @version $Id: URLCodec.java,v 1.19 2004/03/29 07:59:00 ggregory Exp $ + */ +public class URLCodec implements BinaryEncoder, BinaryDecoder, StringEncoder, StringDecoder { + + /** + * The default charset used for string decoding and encoding. + */ + protected String charset = StringEncodings.UTF8; + + protected static byte ESCAPE_CHAR = '%'; + /** + * BitSet of www-form-url safe characters. + */ + protected static final BitSet WWW_FORM_URL = new BitSet(256); + + // Static initializer for www_form_url + static { + // alpha characters + for (int i = 'a'; i <= 'z'; i++) { + WWW_FORM_URL.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + WWW_FORM_URL.set(i); + } + // numeric characters + for (int i = '0'; i <= '9'; i++) { + WWW_FORM_URL.set(i); + } + // special chars + WWW_FORM_URL.set('-'); + WWW_FORM_URL.set('_'); + WWW_FORM_URL.set('.'); + WWW_FORM_URL.set('*'); + // blank to be replaced with + + WWW_FORM_URL.set(' '); + } + + + /** + * Default constructor. + */ + public URLCodec() { + super(); + } + + /** + * Constructor which allows for the selection of a default charset + * + * @param charset the default string charset to use. + */ + public URLCodec(String charset) { + super(); + this.charset = charset; + } + + /** + * Encodes an array of bytes into an array of URL safe 7-bit + * characters. Unsafe characters are escaped. + * + * @param urlsafe bitset of characters deemed URL safe + * @param bytes array of bytes to convert to URL safe characters + * @return array of bytes containing URL safe characters + */ + public static final byte[] encodeUrl(BitSet urlsafe, byte[] bytes) + { + if (bytes == null) { + return null; + } + if (urlsafe == null) { + urlsafe = WWW_FORM_URL; + } + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + if (b < 0) { + b = 256 + b; + } + if (urlsafe.get(b)) { + if (b == ' ') { + b = '+'; + } + buffer.write(b); + } else { + buffer.write('%'); + char hex1 = Character.toUpperCase( + Character.forDigit((b >> 4) & 0xF, 16)); + char hex2 = Character.toUpperCase( + Character.forDigit(b & 0xF, 16)); + buffer.write(hex1); + buffer.write(hex2); + } + } + return buffer.toByteArray(); + } + + + /** + * Decodes an array of URL safe 7-bit characters into an array of + * original bytes. Escaped characters are converted back to their + * original representation. + * + * @param bytes array of URL safe characters + * @return array of original bytes + * @throws DecoderException Thrown if URL decoding is unsuccessful + */ + public static final byte[] decodeUrl(byte[] bytes) + throws DecoderException + { + if (bytes == null) { + return null; + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + if (b == '+') { + buffer.write(' '); + } else if (b == '%') { + try { + int u = Character.digit((char)bytes[++i], 16); + int l = Character.digit((char)bytes[++i], 16); + if (u == -1 || l == -1) { + throw new DecoderException("Invalid URL encoding"); + } + buffer.write((char)((u << 4) + l)); + } catch(ArrayIndexOutOfBoundsException e) { + throw new DecoderException("Invalid URL encoding"); + } + } else { + buffer.write(b); + } + } + return buffer.toByteArray(); + } + + + /** + * Encodes an array of bytes into an array of URL safe 7-bit + * characters. Unsafe characters are escaped. + * + * @param bytes array of bytes to convert to URL safe characters + * @return array of bytes containing URL safe characters + */ + public byte[] encode(byte[] bytes) { + return encodeUrl(WWW_FORM_URL, bytes); + } + + + /** + * Decodes an array of URL safe 7-bit characters into an array of + * original bytes. Escaped characters are converted back to their + * original representation. + * + * @param bytes array of URL safe characters + * @return array of original bytes + * @throws DecoderException Thrown if URL decoding is unsuccessful + */ + public byte[] decode(byte[] bytes) throws DecoderException { + return decodeUrl(bytes); + } + + + /** + * Encodes a string into its URL safe form using the specified + * string charset. Unsafe characters are escaped. + * + * @param pString string to convert to a URL safe form + * @param charset the charset for pString + * @return URL safe string + * @throws UnsupportedEncodingException Thrown if charset is not + * supported + */ + public String encode(String pString, String charset) + throws UnsupportedEncodingException + { + if (pString == null) { + return null; + } + return new String(encode(pString.getBytes(charset)), StringEncodings.US_ASCII); + } + + + /** + * Encodes a string into its URL safe form using the default string + * charset. Unsafe characters are escaped. + * + * @param pString string to convert to a URL safe form + * @return URL safe string + * @throws EncoderException Thrown if URL encoding is unsuccessful + * + * @see #getDefaultCharset() + */ + public String encode(String pString) throws EncoderException { + if (pString == null) { + return null; + } + try { + return encode(pString, getDefaultCharset()); + } catch(UnsupportedEncodingException e) { + throw new EncoderException(e.getMessage()); + } + } + + + /** + * Decodes a URL safe string into its original form using the + * specified encoding. Escaped characters are converted back + * to their original representation. + * + * @param pString URL safe string to convert into its original form + * @param charset the original string charset + * @return original string + * @throws DecoderException Thrown if URL decoding is unsuccessful + * @throws UnsupportedEncodingException Thrown if charset is not + * supported + */ + public String decode(String pString, String charset) + throws DecoderException, UnsupportedEncodingException + { + if (pString == null) { + return null; + } + return new String(decode(pString.getBytes(StringEncodings.US_ASCII)), charset); + } + + + /** + * Decodes a URL safe string into its original form using the default + * string charset. Escaped characters are converted back to their + * original representation. + * + * @param pString URL safe string to convert into its original form + * @return original string + * @throws DecoderException Thrown if URL decoding is unsuccessful + * + * @see #getDefaultCharset() + */ + public String decode(String pString) throws DecoderException { + if (pString == null) { + return null; + } + try { + return decode(pString, getDefaultCharset()); + } catch(UnsupportedEncodingException e) { + throw new DecoderException(e.getMessage()); + } + } + + /** + * Encodes an object into its URL safe form. Unsafe characters are + * escaped. + * + * @param pObject string to convert to a URL safe form + * @return URL safe object + * @throws EncoderException Thrown if URL encoding is not + * applicable to objects of this type or + * if encoding is unsuccessful + */ + public Object encode(Object pObject) throws EncoderException { + if (pObject == null) { + return null; + } else if (pObject instanceof byte[]) { + return encode((byte[])pObject); + } else if (pObject instanceof String) { + return encode((String)pObject); + } else { + throw new EncoderException("Objects of type " + + pObject.getClass().getName() + " cannot be URL encoded"); + + } + } + + /** + * Decodes a URL safe object into its original form. Escaped + * characters are converted back to their original representation. + * + * @param pObject URL safe object to convert into its original form + * @return original object + * @throws DecoderException Thrown if URL decoding is not + * applicable to objects of this type + * if decoding is unsuccessful + */ + public Object decode(Object pObject) throws DecoderException { + if (pObject == null) { + return null; + } else if (pObject instanceof byte[]) { + return decode((byte[])pObject); + } else if (pObject instanceof String) { + return decode((String)pObject); + } else { + throw new DecoderException("Objects of type " + + pObject.getClass().getName() + " cannot be URL decoded"); + + } + } + + /** + * The <code>String</code> encoding used for decoding and encoding. + * + * @return Returns the encoding. + * + * @deprecated use #getDefaultCharset() + */ + public String getEncoding() { + return this.charset; + } + + /** + * The default charset used for string decoding and encoding. + * + * @return the default string charset. + */ + public String getDefaultCharset() { + return this.charset; + } + +} diff --git a/src/org/apache/commons/codec/net/package.html b/src/org/apache/commons/codec/net/package.html new file mode 100644 index 0000000..4607c57 --- /dev/null +++ b/src/org/apache/commons/codec/net/package.html @@ -0,0 +1,22 @@ +<!-- +Copyright 2003-2004 The Apache Software Foundation. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<html> + <body> + <p> + Network related encoding and decoding. + </p> + </body> +</html> diff --git a/src/org/apache/commons/codec/overview.html b/src/org/apache/commons/codec/overview.html new file mode 100644 index 0000000..6b6f6c9 --- /dev/null +++ b/src/org/apache/commons/codec/overview.html @@ -0,0 +1,28 @@ +<!-- +Copyright 2003-2004 The Apache Software Foundation. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<!-- $Id: overview.html,v 1.6 2004/05/17 17:06:10 ggregory Exp $ --> +<html> +<body> +<p> +This document is the API specification for the Apache Jakarta Commons Codec Library, version 1.3. +</p> +<p> +This library requires a JRE version of 1.2.2 or greater. +The hypertext links originating from this document point to Sun's version 1.3 API as the 1.2.2 API documentation +is no longer on-line. +</p> +</body> +</html> diff --git a/src/org/apache/commons/codec/package.html b/src/org/apache/commons/codec/package.html new file mode 100644 index 0000000..b7ccf03 --- /dev/null +++ b/src/org/apache/commons/codec/package.html @@ -0,0 +1,99 @@ +<!-- +Copyright 2003-2004 The Apache Software Foundation. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + <head> + </head> + <body> + <p>A small set of interfaces used by + the various implementations in the sub-packages.</p> + + <p>Definitive implementations of commonly used encoders and decoders.</p> + + <p>Codec is currently comprised of a modest set of utilities and a + simple framework for String encoding and decoding in three categories: + Binary Encoders, Language Encoders, and Network Encoders. </p> + + <h4><a name="Common Encoders">Binary Encoders</a></h4> + + <table border="1" width="100%" cellspacing="2" cellpadding="3"> + <tbody> + <tr> + <td> + <a href="binary/Base64.html"> + org.apache.commons.codec.binary.Base64</a> + </td> + <td> + Provides Base64 content-transfer-encoding as defined in + <a href="http://www.ietf.org/rfc/rfc2045.txt"> RFC 2045</a> + </td> + <td>Production</td> + </tr> + <tr> + <td> + <a href="binary/Hex.html"> + org.apache.commons.codec.binary.Hex</a> + </td> + <td> + Converts an array of bytes into an array of characters + representing the hexidecimal values of each byte in order + </td> + <td>Production</td> + </tr> + </tbody> + </table> + <h4> + <a name="Language Encoders">Language Encoders</a> + </h4> + <p> + Codec contains a number of commonly used language and phonetic + encoders + </p> + <table border="1" width="100%" cellspacing="2" cellpadding="3"> + <tbody> + <tr> + <td> + <a href="#">org.apache.commons.codec.language.Soundex</a> + </td> + <td>Implementation of the Soundex algorithm.</td> + <td>Production</td> + </tr> + <tr> + <td> + <a href="#">org.apache.commons.codec.language.Metaphone</a> + </td> + <td>Implementation of the Metaphone algorithm.</td> + <td>Production</td> + </tr> + </tbody> + </table> + <h4><a name="Network_Encoders">Network Encoders</a></h4> + <h4> </h4> + <p> Codec contains network related encoders </p> + <table border="1" width="100%" cellspacing="2" cellpadding="3"> + <tbody> + <tr> + <td> + <a href="#">org.apache.commons.codec.net.URLCodec</a> + </td> + <td>Implements the 'www-form-urlencoded' encoding scheme.</td> + <td>Production</td> + </tr> + </tbody> + </table> + <br> + </body> +</html> diff --git a/src/org/apache/commons/logging/Log.java b/src/org/apache/commons/logging/Log.java new file mode 100644 index 0000000..9203f3f --- /dev/null +++ b/src/org/apache/commons/logging/Log.java @@ -0,0 +1,245 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging; + +/** + * <p>A simple logging interface abstracting logging APIs. In order to be + * instantiated successfully by {@link LogFactory}, classes that implement + * this interface must have a constructor that takes a single String + * parameter representing the "name" of this Log.</p> + * + * <p> The six logging levels used by <code>Log</code> are (in order): + * <ol> + * <li>trace (the least serious)</li> + * <li>debug</li> + * <li>info</li> + * <li>warn</li> + * <li>error</li> + * <li>fatal (the most serious)</li> + * </ol> + * The mapping of these log levels to the concepts used by the underlying + * logging system is implementation dependent. + * The implemention should ensure, though, that this ordering behaves + * as expected.</p> + * + * <p>Performance is often a logging concern. + * By examining the appropriate property, + * a component can avoid expensive operations (producing information + * to be logged).</p> + * + * <p> For example, + * <code><pre> + * if (log.isDebugEnabled()) { + * ... do something expensive ... + * log.debug(theResult); + * } + * </pre></code> + * </p> + * + * <p>Configuration of the underlying logging system will generally be done + * external to the Logging APIs, through whatever mechanism is supported by + * that system.</p> + * + * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> + * @author Rod Waldhoff + * @version $Id: Log.java 381838 2006-02-28 23:57:11Z skitching $ + */ +public interface Log { + + + // ----------------------------------------------------- Logging Properties + + + /** + * <p> Is debug logging currently enabled? </p> + * + * <p> Call this method to prevent having to perform expensive operations + * (for example, <code>String</code> concatenation) + * when the log level is more than debug. </p> + * + * @return true if debug is enabled in the underlying logger. + */ + public boolean isDebugEnabled(); + + + /** + * <p> Is error logging currently enabled? </p> + * + * <p> Call this method to prevent having to perform expensive operations + * (for example, <code>String</code> concatenation) + * when the log level is more than error. </p> + * + * @return true if error is enabled in the underlying logger. + */ + public boolean isErrorEnabled(); + + + /** + * <p> Is fatal logging currently enabled? </p> + * + * <p> Call this method to prevent having to perform expensive operations + * (for example, <code>String</code> concatenation) + * when the log level is more than fatal. </p> + * + * @return true if fatal is enabled in the underlying logger. + */ + public boolean isFatalEnabled(); + + + /** + * <p> Is info logging currently enabled? </p> + * + * <p> Call this method to prevent having to perform expensive operations + * (for example, <code>String</code> concatenation) + * when the log level is more than info. </p> + * + * @return true if info is enabled in the underlying logger. + */ + public boolean isInfoEnabled(); + + + /** + * <p> Is trace logging currently enabled? </p> + * + * <p> Call this method to prevent having to perform expensive operations + * (for example, <code>String</code> concatenation) + * when the log level is more than trace. </p> + * + * @return true if trace is enabled in the underlying logger. + */ + public boolean isTraceEnabled(); + + + /** + * <p> Is warn logging currently enabled? </p> + * + * <p> Call this method to prevent having to perform expensive operations + * (for example, <code>String</code> concatenation) + * when the log level is more than warn. </p> + * + * @return true if warn is enabled in the underlying logger. + */ + public boolean isWarnEnabled(); + + + // -------------------------------------------------------- Logging Methods + + + /** + * <p> Log a message with trace log level. </p> + * + * @param message log this message + */ + public void trace(Object message); + + + /** + * <p> Log an error with trace log level. </p> + * + * @param message log this message + * @param t log this cause + */ + public void trace(Object message, Throwable t); + + + /** + * <p> Log a message with debug log level. </p> + * + * @param message log this message + */ + public void debug(Object message); + + + /** + * <p> Log an error with debug log level. </p> + * + * @param message log this message + * @param t log this cause + */ + public void debug(Object message, Throwable t); + + + /** + * <p> Log a message with info log level. </p> + * + * @param message log this message + */ + public void info(Object message); + + + /** + * <p> Log an error with info log level. </p> + * + * @param message log this message + * @param t log this cause + */ + public void info(Object message, Throwable t); + + + /** + * <p> Log a message with warn log level. </p> + * + * @param message log this message + */ + public void warn(Object message); + + + /** + * <p> Log an error with warn log level. </p> + * + * @param message log this message + * @param t log this cause + */ + public void warn(Object message, Throwable t); + + + /** + * <p> Log a message with error log level. </p> + * + * @param message log this message + */ + public void error(Object message); + + + /** + * <p> Log an error with error log level. </p> + * + * @param message log this message + * @param t log this cause + */ + public void error(Object message, Throwable t); + + + /** + * <p> Log a message with fatal log level. </p> + * + * @param message log this message + */ + public void fatal(Object message); + + + /** + * <p> Log an error with fatal log level. </p> + * + * @param message log this message + * @param t log this cause + */ + public void fatal(Object message, Throwable t); + + +} diff --git a/src/org/apache/commons/logging/LogConfigurationException.java b/src/org/apache/commons/logging/LogConfigurationException.java new file mode 100644 index 0000000..b34387b --- /dev/null +++ b/src/org/apache/commons/logging/LogConfigurationException.java @@ -0,0 +1,97 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.logging; + + +/** + * <p>An exception that is thrown only if a suitable <code>LogFactory</code> + * or <code>Log</code> instance cannot be created by the corresponding + * factory methods.</p> + * + * @author Craig R. McClanahan + * @version $Revision: 155426 $ $Date: 2005-02-26 13:10:49 +0000 (Sat, 26 Feb 2005) $ + */ + +public class LogConfigurationException extends RuntimeException { + + + /** + * Construct a new exception with <code>null</code> as its detail message. + */ + public LogConfigurationException() { + + super(); + + } + + + /** + * Construct a new exception with the specified detail message. + * + * @param message The detail message + */ + public LogConfigurationException(String message) { + + super(message); + + } + + + /** + * Construct a new exception with the specified cause and a derived + * detail message. + * + * @param cause The underlying cause + */ + public LogConfigurationException(Throwable cause) { + + this((cause == null) ? null : cause.toString(), cause); + + } + + + /** + * Construct a new exception with the specified detail message and cause. + * + * @param message The detail message + * @param cause The underlying cause + */ + public LogConfigurationException(String message, Throwable cause) { + + super(message + " (Caused by " + cause + ")"); + this.cause = cause; // Two-argument version requires JDK 1.4 or later + + } + + + /** + * The underlying cause of this exception. + */ + protected Throwable cause = null; + + + /** + * Return the underlying cause of this exception (if any). + */ + public Throwable getCause() { + + return (this.cause); + + } + + +} diff --git a/src/org/apache/commons/logging/LogFactory.java b/src/org/apache/commons/logging/LogFactory.java new file mode 100644 index 0000000..107d0f7 --- /dev/null +++ b/src/org/apache/commons/logging/LogFactory.java @@ -0,0 +1,1740 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.logging; + + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; + + +/** + * <p>Factory for creating {@link Log} instances, with discovery and + * configuration features similar to that employed by standard Java APIs + * such as JAXP.</p> + * + * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is heavily + * based on the SAXParserFactory and DocumentBuilderFactory implementations + * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.</p> + * + * @author Craig R. McClanahan + * @author Costin Manolache + * @author Richard A. Sitze + * @version $Revision: 399431 $ $Date: 2006-05-03 21:58:34 +0100 (Wed, 03 May 2006) $ + */ + +public abstract class LogFactory { + + + // ----------------------------------------------------- Manifest Constants + + /** + * The name (<code>priority</code>) of the key in the config file used to + * specify the priority of that particular config file. The associated value + * is a floating-point number; higher values take priority over lower values. + */ + public static final String PRIORITY_KEY = "priority"; + + /** + * The name (<code>use_tccl</code>) of the key in the config file used + * to specify whether logging classes should be loaded via the thread + * context class loader (TCCL), or not. By default, the TCCL is used. + */ + public static final String TCCL_KEY = "use_tccl"; + + /** + * The name (<code>org.apache.commons.logging.LogFactory</code>) of the property + * used to identify the LogFactory implementation + * class name. This can be used as a system property, or as an entry in a + * configuration properties file. + */ + public static final String FACTORY_PROPERTY = + "org.apache.commons.logging.LogFactory"; + + /** + * The fully qualified class name of the fallback <code>LogFactory</code> + * implementation class to use, if no other can be found. + */ + public static final String FACTORY_DEFAULT = + "org.apache.commons.logging.impl.LogFactoryImpl"; + + /** + * The name (<code>commons-logging.properties</code>) of the properties file to search for. + */ + public static final String FACTORY_PROPERTIES = + "commons-logging.properties"; + + /** + * JDK1.3+ <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service%20Provider"> + * 'Service Provider' specification</a>. + * + */ + protected static final String SERVICE_ID = + "META-INF/services/org.apache.commons.logging.LogFactory"; + + /** + * The name (<code>org.apache.commons.logging.diagnostics.dest</code>) + * of the property used to enable internal commons-logging + * diagnostic output, in order to get information on what logging + * implementations are being discovered, what classloaders they + * are loaded through, etc. + * <p> + * If a system property of this name is set then the value is + * assumed to be the name of a file. The special strings + * STDOUT or STDERR (case-sensitive) indicate output to + * System.out and System.err respectively. + * <p> + * Diagnostic logging should be used only to debug problematic + * configurations and should not be set in normal production use. + */ + public static final String DIAGNOSTICS_DEST_PROPERTY = + "org.apache.commons.logging.diagnostics.dest"; + + /** + * When null (the usual case), no diagnostic output will be + * generated by LogFactory or LogFactoryImpl. When non-null, + * interesting events will be written to the specified object. + */ + private static PrintStream diagnosticsStream = null; + + /** + * A string that gets prefixed to every message output by the + * logDiagnostic method, so that users can clearly see which + * LogFactory class is generating the output. + */ + private static String diagnosticPrefix; + + /** + * <p>Setting this system property + * (<code>org.apache.commons.logging.LogFactory.HashtableImpl</code>) + * value allows the <code>Hashtable</code> used to store + * classloaders to be substituted by an alternative implementation. + * </p> + * <p> + * <strong>Note:</strong> <code>LogFactory</code> will print: + * <code><pre> + * [ERROR] LogFactory: Load of custom hashtable failed</em> + * </pre></code> + * to system error and then continue using a standard Hashtable. + * </p> + * <p> + * <strong>Usage:</strong> Set this property when Java is invoked + * and <code>LogFactory</code> will attempt to load a new instance + * of the given implementation class. + * For example, running the following ant scriplet: + * <code><pre> + * <java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}"> + * ... + * <sysproperty + * key="org.apache.commons.logging.LogFactory.HashtableImpl" + * value="org.apache.commons.logging.AltHashtable"/> + * </java> + * </pre></code> + * will mean that <code>LogFactory</code> will load an instance of + * <code>org.apache.commons.logging.AltHashtable</code>. + * </p> + * <p> + * A typical use case is to allow a custom + * Hashtable implementation using weak references to be substituted. + * This will allow classloaders to be garbage collected without + * the need to release them (on 1.3+ JVMs only, of course ;) + * </p> + */ + public static final String HASHTABLE_IMPLEMENTATION_PROPERTY = + "org.apache.commons.logging.LogFactory.HashtableImpl"; + /** Name used to load the weak hashtable implementation by names */ + private static final String WEAK_HASHTABLE_CLASSNAME = + "org.apache.commons.logging.impl.WeakHashtable"; + + /** + * A reference to the classloader that loaded this class. This is the + * same as LogFactory.class.getClassLoader(). However computing this + * value isn't quite as simple as that, as we potentially need to use + * AccessControllers etc. It's more efficient to compute it once and + * cache it here. + */ + private static ClassLoader thisClassLoader; + + // ----------------------------------------------------------- Constructors + + + /** + * Protected constructor that is not available for public use. + */ + protected LogFactory() { + } + + // --------------------------------------------------------- Public Methods + + + /** + * Return the configuration attribute with the specified name (if any), + * or <code>null</code> if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public abstract Object getAttribute(String name); + + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public abstract String[] getAttributeNames(); + + + /** + * Convenience method to derive a name from the specified class and + * call <code>getInstance(String)</code> with it. + * + * @param clazz Class for which a suitable Log name will be derived + * + * @exception LogConfigurationException if a suitable <code>Log</code> + * instance cannot be returned + */ + public abstract Log getInstance(Class clazz) + throws LogConfigurationException; + + + /** + * <p>Construct (if necessary) and return a <code>Log</code> instance, + * using the factory's current set of configuration attributes.</p> + * + * <p><strong>NOTE</strong> - Depending upon the implementation of + * the <code>LogFactory</code> you are using, the <code>Log</code> + * instance you are returned may or may not be local to the current + * application, and may or may not be returned again on a subsequent + * call with the same name argument.</p> + * + * @param name Logical name of the <code>Log</code> instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable <code>Log</code> + * instance cannot be returned + */ + public abstract Log getInstance(String name) + throws LogConfigurationException; + + + /** + * Release any internal references to previously created {@link Log} + * instances returned by this factory. This is useful in environments + * like servlet containers, which implement application reloading by + * throwing away a ClassLoader. Dangling references to objects in that + * class loader would prevent garbage collection. + */ + public abstract void release(); + + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public abstract void removeAttribute(String name); + + + /** + * Set the configuration attribute with the specified name. Calling + * this with a <code>null</code> value is equivalent to calling + * <code>removeAttribute(name)</code>. + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or <code>null</code> + * to remove any setting for this attribute + */ + public abstract void setAttribute(String name, Object value); + + + // ------------------------------------------------------- Static Variables + + + /** + * The previously constructed <code>LogFactory</code> instances, keyed by + * the <code>ClassLoader</code> with which it was created. + */ + protected static Hashtable factories = null; + + /** + * Prevously constructed <code>LogFactory</code> instance as in the + * <code>factories</code> map, but for the case where + * <code>getClassLoader</code> returns <code>null</code>. + * This can happen when: + * <ul> + * <li>using JDK1.1 and the calling code is loaded via the system + * classloader (very common)</li> + * <li>using JDK1.2+ and the calling code is loaded via the boot + * classloader (only likely for embedded systems work).</li> + * </ul> + * Note that <code>factories</code> is a <i>Hashtable</i> (not a HashMap), + * and hashtables don't allow null as a key. + */ + protected static LogFactory nullClassLoaderFactory = null; + + /** + * Create the hashtable which will be used to store a map of + * (context-classloader -> logfactory-object). Version 1.2+ of Java + * supports "weak references", allowing a custom Hashtable class + * to be used which uses only weak references to its keys. Using weak + * references can fix memory leaks on webapp unload in some cases (though + * not all). Version 1.1 of Java does not support weak references, so we + * must dynamically determine which we are using. And just for fun, this + * code also supports the ability for a system property to specify an + * arbitrary Hashtable implementation name. + * <p> + * Note that the correct way to ensure no memory leaks occur is to ensure + * that LogFactory.release(contextClassLoader) is called whenever a + * webapp is undeployed. + */ + private static final Hashtable createFactoryStore() { + Hashtable result = null; + String storeImplementationClass + = System.getProperty(HASHTABLE_IMPLEMENTATION_PROPERTY); + if (storeImplementationClass == null) { + storeImplementationClass = WEAK_HASHTABLE_CLASSNAME; + } + try { + Class implementationClass = Class.forName(storeImplementationClass); + result = (Hashtable) implementationClass.newInstance(); + + } catch (Throwable t) { + // ignore + if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) { + // if the user's trying to set up a custom implementation, give a clue + if (isDiagnosticsEnabled()) { + // use internal logging to issue the warning + logDiagnostic("[ERROR] LogFactory: Load of custom hashtable failed"); + } else { + // we *really* want this output, even if diagnostics weren't + // explicitly enabled by the user. + System.err.println("[ERROR] LogFactory: Load of custom hashtable failed"); + } + } + } + if (result == null) { + result = new Hashtable(); + } + return result; + } + + + // --------------------------------------------------------- Static Methods + + /** + * <p>Construct (if necessary) and return a <code>LogFactory</code> + * instance, using the following ordered lookup procedure to determine + * the name of the implementation class to be loaded.</p> + * <ul> + * <li>The <code>org.apache.commons.logging.LogFactory</code> system + * property.</li> + * <li>The JDK 1.3 Service Discovery mechanism</li> + * <li>Use the properties file <code>commons-logging.properties</code> + * file, if found in the class path of this class. The configuration + * file is in standard <code>java.util.Properties</code> format and + * contains the fully qualified name of the implementation class + * with the key being the system property defined above.</li> + * <li>Fall back to a default implementation class + * (<code>org.apache.commons.logging.impl.LogFactoryImpl</code>).</li> + * </ul> + * + * <p><em>NOTE</em> - If the properties file method of identifying the + * <code>LogFactory</code> implementation class is utilized, all of the + * properties defined in this file will be set as configuration attributes + * on the corresponding <code>LogFactory</code> instance.</p> + * + * <p><em>NOTE</em> - In a multithreaded environment it is possible + * that two different instances will be returned for the same + * classloader environment. + * </p> + * + * @exception LogConfigurationException if the implementation class is not + * available or cannot be instantiated. + */ + public static LogFactory getFactory() throws LogConfigurationException { + // Identify the class loader we will be using + ClassLoader contextClassLoader = getContextClassLoader(); + + if (contextClassLoader == null) { + // This is an odd enough situation to report about. This + // output will be a nuisance on JDK1.1, as the system + // classloader is null in that environment. + if (isDiagnosticsEnabled()) { + logDiagnostic("Context classloader is null."); + } + } + + // Return any previously registered factory for this class loader + LogFactory factory = getCachedFactory(contextClassLoader); + if (factory != null) { + return factory; + } + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] LogFactory implementation requested for the first time for context classloader " + + objectId(contextClassLoader)); + logHierarchy("[LOOKUP] ", contextClassLoader); + } + + // Load properties file. + // + // If the properties file exists, then its contents are used as + // "attributes" on the LogFactory implementation class. One particular + // property may also control which LogFactory concrete subclass is + // used, but only if other discovery mechanisms fail.. + // + // As the properties file (if it exists) will be used one way or + // another in the end we may as well look for it first. + + Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES); + + // Determine whether we will be using the thread context class loader to + // load logging classes or not by checking the loaded properties file (if any). + ClassLoader baseClassLoader = contextClassLoader; + if (props != null) { + String useTCCLStr = props.getProperty(TCCL_KEY); + if (useTCCLStr != null) { + // The Boolean.valueOf(useTCCLStr).booleanValue() formulation + // is required for Java 1.2 compatability. + if (Boolean.valueOf(useTCCLStr).booleanValue() == false) { + // Don't use current context classloader when locating any + // LogFactory or Log classes, just use the class that loaded + // this abstract class. When this class is deployed in a shared + // classpath of a container, it means webapps cannot deploy their + // own logging implementations. It also means that it is up to the + // implementation whether to load library-specific config files + // from the TCCL or not. + baseClassLoader = thisClassLoader; + } + } + } + + // Determine which concrete LogFactory subclass to use. + // First, try a global system property + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + + "] to define the LogFactory subclass to use..."); + } + + try { + String factoryClass = System.getProperty(FACTORY_PROPERTY); + if (factoryClass != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Creating an instance of LogFactory class '" + factoryClass + + "' as specified by system property " + FACTORY_PROPERTY); + } + + factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] No system property [" + FACTORY_PROPERTY + + "] defined."); + } + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] A security exception occurred while trying to create an" + + " instance of the custom factory class" + + ": [" + e.getMessage().trim() + + "]. Trying alternative implementations..."); + } + ; // ignore + } catch(RuntimeException e) { + // This is not consistent with the behaviour when a bad LogFactory class is + // specified in a services file. + // + // One possible exception that can occur here is a ClassCastException when + // the specified class wasn't castable to this LogFactory type. + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] An exception occurred while trying to create an" + + " instance of the custom factory class" + + ": [" + e.getMessage().trim() + + "] as specified by a system property."); + } + throw e; + } + + + // Second, try to find a service by using the JDK1.3 class + // discovery mechanism, which involves putting a file with the name + // of an interface class in the META-INF/services directory, where the + // contents of the file is a single line specifying a concrete class + // that implements the desired interface. + + if (factory == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + + "] to define the LogFactory subclass to use..."); + } + try { + InputStream is = getResourceAsStream(contextClassLoader, + SERVICE_ID); + + if( is != null ) { + // This code is needed by EBCDIC and other strange systems. + // It's a fix for bugs reported in xerces + BufferedReader rd; + try { + rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + rd = new BufferedReader(new InputStreamReader(is)); + } + + String factoryClassName = rd.readLine(); + rd.close(); + + if (factoryClassName != null && + ! "".equals(factoryClassName)) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Creating an instance of LogFactory class " + factoryClassName + + " as specified by file '" + SERVICE_ID + + "' which was present in the path of the context" + + " classloader."); + } + factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader ); + } + } else { + // is == null + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] No resource file with name '" + SERVICE_ID + + "' found."); + } + } + } catch( Exception ex ) { + // note: if the specified LogFactory class wasn't compatible with LogFactory + // for some reason, a ClassCastException will be caught here, and attempts will + // continue to find a compatible class. + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] A security exception occurred while trying to create an" + + " instance of the custom factory class" + + ": [" + ex.getMessage().trim() + + "]. Trying alternative implementations..."); + } + ; // ignore + } + } + + + // Third try looking into the properties file read earlier (if found) + + if (factory == null) { + if (props != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Looking in properties file for entry with key '" + + FACTORY_PROPERTY + + "' to define the LogFactory subclass to use..."); + } + String factoryClass = props.getProperty(FACTORY_PROPERTY); + if (factoryClass != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Properties file specifies LogFactory subclass '" + + factoryClass + "'"); + } + factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); + + // TODO: think about whether we need to handle exceptions from newFactory + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Properties file has no entry specifying LogFactory subclass."); + } + } + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] No properties file available to determine" + + " LogFactory subclass from.."); + } + } + } + + + // Fourth, try the fallback implementation class + + if (factory == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT + + "' via the same classloader that loaded this LogFactory" + + " class (ie not looking in the context classloader)."); + } + + // Note: unlike the above code which can try to load custom LogFactory + // implementations via the TCCL, we don't try to load the default LogFactory + // implementation via the context classloader because: + // * that can cause problems (see comments in newFactory method) + // * no-one should be customising the code of the default class + // Yes, we do give up the ability for the child to ship a newer + // version of the LogFactoryImpl class and have it used dynamically + // by an old LogFactory class in the parent, but that isn't + // necessarily a good idea anyway. + factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); + } + + if (factory != null) { + /** + * Always cache using context class loader. + */ + cacheFactory(contextClassLoader, factory); + + if( props!=null ) { + Enumeration names = props.propertyNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + String value = props.getProperty(name); + factory.setAttribute(name, value); + } + } + } + + return factory; + } + + + /** + * Convenience method to return a named logger, without the application + * having to care about factories. + * + * @param clazz Class from which a log name will be derived + * + * @exception LogConfigurationException if a suitable <code>Log</code> + * instance cannot be returned + */ + public static Log getLog(Class clazz) + throws LogConfigurationException { + + // BEGIN android-added + return getLog(clazz.getName()); + // END android-added + // BEGIN android-deleted + //return (getFactory().getInstance(clazz)); + // END android-deleted + + } + + + /** + * Convenience method to return a named logger, without the application + * having to care about factories. + * + * @param name Logical name of the <code>Log</code> instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable <code>Log</code> + * instance cannot be returned + */ + public static Log getLog(String name) + throws LogConfigurationException { + + // BEGIN android-added + return new org.apache.commons.logging.impl.Jdk14Logger(name); + // END android-added + // BEGIN android-deleted + //return (getFactory().getInstance(name)); + // END android-deleted + + } + + + /** + * Release any internal references to previously created {@link LogFactory} + * instances that have been associated with the specified class loader + * (if any), after calling the instance method <code>release()</code> on + * each of them. + * + * @param classLoader ClassLoader for which to release the LogFactory + */ + public static void release(ClassLoader classLoader) { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for classloader " + objectId(classLoader)); + } + synchronized (factories) { + if (classLoader == null) { + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } else { + LogFactory factory = (LogFactory) factories.get(classLoader); + if (factory != null) { + factory.release(); + factories.remove(classLoader); + } + } + } + + } + + + /** + * Release any internal references to previously created {@link LogFactory} + * instances, after calling the instance method <code>release()</code> on + * each of them. This is useful in environments like servlet containers, + * which implement application reloading by throwing away a ClassLoader. + * Dangling references to objects in that class loader would prevent + * garbage collection. + */ + public static void releaseAll() { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for all classloaders."); + } + synchronized (factories) { + Enumeration elements = factories.elements(); + while (elements.hasMoreElements()) { + LogFactory element = (LogFactory) elements.nextElement(); + element.release(); + } + factories.clear(); + + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } + + } + + + // ------------------------------------------------------ Protected Methods + + /** + * Safely get access to the classloader for the specified class. + * <p> + * Theoretically, calling getClassLoader can throw a security exception, + * and so should be done under an AccessController in order to provide + * maximum flexibility. However in practice people don't appear to use + * security policies that forbid getClassLoader calls. So for the moment + * all code is written to call this method rather than Class.getClassLoader, + * so that we could put AccessController stuff in this method without any + * disruption later if we need to. + * <p> + * Even when using an AccessController, however, this method can still + * throw SecurityException. Commons-logging basically relies on the + * ability to access classloaders, ie a policy that forbids all + * classloader access will also prevent commons-logging from working: + * currently this method will throw an exception preventing the entire app + * from starting up. Maybe it would be good to detect this situation and + * just disable all commons-logging? Not high priority though - as stated + * above, security policies that prevent classloader access aren't common. + * + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + try { + return clazz.getClassLoader(); + } catch(SecurityException ex) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Unable to get classloader for class '" + clazz + + "' due to security restrictions - " + ex.getMessage()); + } + throw ex; + } + } + + /** + * Calls LogFactory.directGetContextClassLoader under the control of an + * AccessController class. This means that java code running under a + * security manager that forbids access to ClassLoaders will still work + * if this class is given appropriate privileges, even when the caller + * doesn't have such privileges. Without using an AccessController, the + * the entire call stack must have the privilege before the call is + * allowed. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + * + * @throws SecurityException if the current java security policy doesn't + * allow this class to access the context classloader. + */ + protected static ClassLoader getContextClassLoader() + throws LogConfigurationException { + + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return directGetContextClassLoader(); + } + }); + } + + /** + * Return the thread context class loader if available; otherwise return + * null. + * <p> + * Most/all code should call getContextClassLoader rather than calling + * this method directly. + * <p> + * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + * <p> + * Note that no internal logging is done within this method because + * this method is called every time LogFactory.getLogger() is called, + * and we don't want too much output generated here. + * + * @exception LogConfigurationException if a suitable class loader + * cannot be identified. + * + * @exception SecurityException if the java security policy forbids + * access to the context classloader from one of the classes in the + * current call stack. + * @since 1.1 + */ + protected static ClassLoader directGetContextClassLoader() + throws LogConfigurationException + { + ClassLoader classLoader = null; + + try { + // Are we running on a JDK 1.2 or later system? + Method method = Thread.class.getMethod("getContextClassLoader", + (Class[]) null); + + // Get the thread context class loader (if there is one) + try { + classLoader = (ClassLoader)method.invoke(Thread.currentThread(), + (Object[]) null); + } catch (IllegalAccessException e) { + throw new LogConfigurationException + ("Unexpected IllegalAccessException", e); + } catch (InvocationTargetException e) { + /** + * InvocationTargetException is thrown by 'invoke' when + * the method being invoked (getContextClassLoader) throws + * an exception. + * + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * In the first case (not related), we want to ignore and + * keep going. We cannot help but also ignore the second + * with the logic below, but other calls elsewhere (to + * obtain a class loader) will trigger this exception where + * we can make a distinction. + */ + if (e.getTargetException() instanceof SecurityException) { + ; // ignore + } else { + // Capture 'e.getTargetException()' exception for details + // alternate: log 'e.getTargetException()', and pass back 'e'. + throw new LogConfigurationException + ("Unexpected InvocationTargetException", e.getTargetException()); + } + } + } catch (NoSuchMethodException e) { + // Assume we are running on JDK 1.1 + classLoader = getClassLoader(LogFactory.class); + + // We deliberately don't log a message here to outputStream; + // this message would be output for every call to LogFactory.getLog() + // when running on JDK1.1 + // + // if (outputStream != null) { + // outputStream.println( + // "Method Thread.getContextClassLoader does not exist;" + // + " assuming this is JDK 1.1, and that the context" + // + " classloader is the same as the class that loaded" + // + " the concrete LogFactory class."); + // } + + } + + // Return the selected class loader + return classLoader; + } + + /** + * Check cached factories (keyed by contextClassLoader) + * + * @param contextClassLoader is the context classloader associated + * with the current thread. This allows separate LogFactory objects + * per component within a container, provided each component has + * a distinct context classloader set. This parameter may be null + * in JDK1.1, and in embedded systems where jcl-using code is + * placed in the bootclasspath. + * + * @return the factory associated with the specified classloader if + * one has previously been created, or null if this is the first time + * we have seen this particular classloader. + */ + private static LogFactory getCachedFactory(ClassLoader contextClassLoader) + { + LogFactory factory = null; + + if (contextClassLoader == null) { + // We have to handle this specially, as factories is a Hashtable + // and those don't accept null as a key value. + // + // nb: nullClassLoaderFactory might be null. That's ok. + factory = nullClassLoaderFactory; + } else { + factory = (LogFactory) factories.get(contextClassLoader); + } + + return factory; + } + + /** + * Remember this factory, so later calls to LogFactory.getCachedFactory + * can return the previously created object (together with all its + * cached Log objects). + * + * @param classLoader should be the current context classloader. Note that + * this can be null under some circumstances; this is ok. + * + * @param factory should be the factory to cache. This should never be null. + */ + private static void cacheFactory(ClassLoader classLoader, LogFactory factory) + { + // Ideally we would assert(factory != null) here. However reporting + // errors from within a logging implementation is a little tricky! + + if (factory != null) { + if (classLoader == null) { + nullClassLoaderFactory = factory; + } else { + factories.put(classLoader, factory); + } + } + } + + /** + * Return a new instance of the specified <code>LogFactory</code> + * implementation class, loaded by the specified class loader. + * If that fails, try the class loader used to load this + * (abstract) LogFactory. + * <p> + * <h2>ClassLoader conflicts</h2> + * Note that there can be problems if the specified ClassLoader is not the + * same as the classloader that loaded this class, ie when loading a + * concrete LogFactory subclass via a context classloader. + * <p> + * The problem is the same one that can occur when loading a concrete Log + * subclass via a context classloader. + * <p> + * The problem occurs when code running in the context classloader calls + * class X which was loaded via a parent classloader, and class X then calls + * LogFactory.getFactory (either directly or via LogFactory.getLog). Because + * class X was loaded via the parent, it binds to LogFactory loaded via + * the parent. When the code in this method finds some LogFactoryYYYY + * class in the child (context) classloader, and there also happens to be a + * LogFactory class defined in the child classloader, then LogFactoryYYYY + * will be bound to LogFactory@childloader. It cannot be cast to + * LogFactory@parentloader, ie this method cannot return the object as + * the desired type. Note that it doesn't matter if the LogFactory class + * in the child classloader is identical to the LogFactory class in the + * parent classloader, they are not compatible. + * <p> + * The solution taken here is to simply print out an error message when + * this occurs then throw an exception. The deployer of the application + * must ensure they remove all occurrences of the LogFactory class from + * the child classloader in order to resolve the issue. Note that they + * do not have to move the custom LogFactory subclass; that is ok as + * long as the only LogFactory class it can find to bind to is in the + * parent classloader. + * <p> + * @param factoryClass Fully qualified name of the <code>LogFactory</code> + * implementation class + * @param classLoader ClassLoader from which to load this class + * @param contextClassLoader is the context that this new factory will + * manage logging for. + * + * @exception LogConfigurationException if a suitable instance + * cannot be created + * @since 1.1 + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader, + final ClassLoader contextClassLoader) + throws LogConfigurationException + { + // Note that any unchecked exceptions thrown by the createFactory + // method will propagate out of this method; in particular a + // ClassCastException can be thrown. + Object result = AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return createFactory(factoryClass, classLoader); + } + }); + + if (result instanceof LogConfigurationException) { + LogConfigurationException ex = (LogConfigurationException) result; + if (isDiagnosticsEnabled()) { + logDiagnostic( + "An error occurred while loading the factory class:" + + ex.getMessage()); + } + throw ex; + } + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Created object " + objectId(result) + + " to manage classloader " + objectId(contextClassLoader)); + } + return (LogFactory)result; + } + + /** + * Method provided for backwards compatibility; see newFactory version that + * takes 3 parameters. + * <p> + * This method would only ever be called in some rather odd situation. + * Note that this method is static, so overriding in a subclass doesn't + * have any effect unless this method is called from a method in that + * subclass. However this method only makes sense to use from the + * getFactory method, and as that is almost always invoked via + * LogFactory.getFactory, any custom definition in a subclass would be + * pointless. Only a class with a custom getFactory method, then invoked + * directly via CustomFactoryImpl.getFactory or similar would ever call + * this. Anyway, it's here just in case, though the "managed class loader" + * value output to the diagnostics will not report the correct value. + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader) { + return newFactory(factoryClass, classLoader, null); + } + + /** + * Implements the operations described in the javadoc for newFactory. + * + * @param factoryClass + * + * @param classLoader used to load the specified factory class. This is + * expected to be either the TCCL or the classloader which loaded this + * class. Note that the classloader which loaded this class might be + * "null" (ie the bootloader) for embedded systems. + * + * @return either a LogFactory object or a LogConfigurationException object. + * @since 1.1 + */ + protected static Object createFactory(String factoryClass, ClassLoader classLoader) { + + // This will be used to diagnose bad configurations + // and allow a useful message to be sent to the user + Class logFactoryClass = null; + try { + if (classLoader != null) { + try { + // First the given class loader param (thread class loader) + + // Warning: must typecast here & allow exception + // to be generated/caught & recast properly. + logFactoryClass = classLoader.loadClass(factoryClass); + if (LogFactory.class.isAssignableFrom(logFactoryClass)) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Loaded class " + logFactoryClass.getName() + + " from classloader " + objectId(classLoader)); + } + } else { + // + // This indicates a problem with the ClassLoader tree. + // An incompatible ClassLoader was used to load the + // implementation. + // As the same classes + // must be available in multiple class loaders, + // it is very likely that multiple JCL jars are present. + // The most likely fix for this + // problem is to remove the extra JCL jars from the + // ClassLoader hierarchy. + // + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Factory class " + logFactoryClass.getName() + + " loaded from classloader " + objectId(logFactoryClass.getClassLoader()) + + " does not extend '" + LogFactory.class.getName() + + "' as loaded by this classloader."); + logHierarchy("[BAD CL TREE] ", classLoader); + } + } + + return (LogFactory) logFactoryClass.newInstance(); + + } catch (ClassNotFoundException ex) { + if (classLoader == thisClassLoader) { + // Nothing more to try, onwards. + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Unable to locate any class called '" + factoryClass + + "' via classloader " + objectId(classLoader)); + } + throw ex; + } + // ignore exception, continue + } catch (NoClassDefFoundError e) { + if (classLoader == thisClassLoader) { + // Nothing more to try, onwards. + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Class '" + factoryClass + "' cannot be loaded" + + " via classloader " + objectId(classLoader) + + " - it depends on some other class that cannot" + + " be found."); + } + throw e; + } + // ignore exception, continue + } catch(ClassCastException e) { + if (classLoader == thisClassLoader) { + // There's no point in falling through to the code below that + // tries again with thisClassLoader, because we've just tried + // loading with that loader (not the TCCL). Just throw an + // appropriate exception here. + + final boolean implementsLogFactory = implementsLogFactory(logFactoryClass); + + // + // Construct a good message: users may not actual expect that a custom implementation + // has been specified. Several well known containers use this mechanism to adapt JCL + // to their native logging system. + // + String msg = + "The application has specified that a custom LogFactory implementation should be used but " + + "Class '" + factoryClass + "' cannot be converted to '" + + LogFactory.class.getName() + "'. "; + if (implementsLogFactory) { + msg = msg + "The conflict is caused by the presence of multiple LogFactory classes in incompatible classloaders. " + + "Background can be found in http://jakarta.apache.org/commons/logging/tech.html. " + + "If you have not explicitly specified a custom LogFactory then it is likely that " + + "the container has set one without your knowledge. " + + "In this case, consider using the commons-logging-adapters.jar file or " + + "specifying the standard LogFactory from the command line. "; + } else { + msg = msg + "Please check the custom implementation. "; + } + msg = msg + "Help can be found @http://jakarta.apache.org/commons/logging/troubleshooting.html."; + + if (isDiagnosticsEnabled()) { + logDiagnostic(msg); + } + + ClassCastException ex = new ClassCastException(msg); + throw ex; + } + + // Ignore exception, continue. Presumably the classloader was the + // TCCL; the code below will try to load the class via thisClassLoader. + // This will handle the case where the original calling class is in + // a shared classpath but the TCCL has a copy of LogFactory and the + // specified LogFactory implementation; we will fall back to using the + // LogFactory implementation from the same classloader as this class. + // + // Issue: this doesn't handle the reverse case, where this LogFactory + // is in the webapp, and the specified LogFactory implementation is + // in a shared classpath. In that case: + // (a) the class really does implement LogFactory (bad log msg above) + // (b) the fallback code will result in exactly the same problem. + } + } + + /* At this point, either classLoader == null, OR + * classLoader was unable to load factoryClass. + * + * In either case, we call Class.forName, which is equivalent + * to LogFactory.class.getClassLoader().load(name), ie we ignore + * the classloader parameter the caller passed, and fall back + * to trying the classloader associated with this class. See the + * javadoc for the newFactory method for more info on the + * consequences of this. + * + * Notes: + * * LogFactory.class.getClassLoader() may return 'null' + * if LogFactory is loaded by the bootstrap classloader. + */ + // Warning: must typecast here & allow exception + // to be generated/caught & recast properly. + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Unable to load factory class via classloader " + + objectId(classLoader) + + " - trying the classloader associated with this LogFactory."); + } + logFactoryClass = Class.forName(factoryClass); + return (LogFactory) logFactoryClass.newInstance(); + } catch (Exception e) { + // Check to see if we've got a bad configuration + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to create LogFactory instance."); + } + if (logFactoryClass != null + && !LogFactory.class.isAssignableFrom(logFactoryClass)) { + + return new LogConfigurationException( + "The chosen LogFactory implementation does not extend LogFactory." + + " Please check your configuration.", + e); + } + return new LogConfigurationException(e); + } + } + + /** + * Determines whether the given class actually implements <code>LogFactory</code>. + * Diagnostic information is also logged. + * <p> + * <strong>Usage:</strong> to diagnose whether a classloader conflict is the cause + * of incompatibility. The test used is whether the class is assignable from + * the <code>LogFactory</code> class loaded by the class's classloader. + * @param logFactoryClass <code>Class</code> which may implement <code>LogFactory</code> + * @return true if the <code>logFactoryClass</code> does extend + * <code>LogFactory</code> when that class is loaded via the same + * classloader that loaded the <code>logFactoryClass</code>. + */ + private static boolean implementsLogFactory(Class logFactoryClass) { + boolean implementsLogFactory = false; + if (logFactoryClass != null) { + try { + ClassLoader logFactoryClassLoader = logFactoryClass.getClassLoader(); + if (logFactoryClassLoader == null) { + logDiagnostic("[CUSTOM LOG FACTORY] was loaded by the boot classloader"); + } else { + logHierarchy("[CUSTOM LOG FACTORY] ", logFactoryClassLoader); + Class factoryFromCustomLoader + = Class.forName("org.apache.commons.logging.LogFactory", false, logFactoryClassLoader); + implementsLogFactory = factoryFromCustomLoader.isAssignableFrom(logFactoryClass); + if (implementsLogFactory) { + logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() + + " implements LogFactory but was loaded by an incompatible classloader."); + } else { + logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() + + " does not implement LogFactory."); + } + } + } catch (SecurityException e) { + // + // The application is running within a hostile security environment. + // This will make it very hard to diagnose issues with JCL. + // Consider running less securely whilst debugging this issue. + // + logDiagnostic("[CUSTOM LOG FACTORY] SecurityException thrown whilst trying to determine whether " + + "the compatibility was caused by a classloader conflict: " + + e.getMessage()); + } catch (LinkageError e) { + // + // This should be an unusual circumstance. + // LinkageError's usually indicate that a dependent class has incompatibly changed. + // Another possibility may be an exception thrown by an initializer. + // Time for a clean rebuild? + // + logDiagnostic("[CUSTOM LOG FACTORY] LinkageError thrown whilst trying to determine whether " + + "the compatibility was caused by a classloader conflict: " + + e.getMessage()); + } catch (ClassNotFoundException e) { + // + // LogFactory cannot be loaded by the classloader which loaded the custom factory implementation. + // The custom implementation is not viable until this is corrected. + // Ensure that the JCL jar and the custom class are available from the same classloader. + // Running with diagnostics on should give information about the classloaders used + // to load the custom factory. + // + logDiagnostic("[CUSTOM LOG FACTORY] LogFactory class cannot be loaded by classloader which loaded the " + + "custom LogFactory implementation. Is the custom factory in the right classloader?"); + } + } + return implementsLogFactory; + } + + /** + * Applets may run in an environment where accessing resources of a loader is + * a secure operation, but where the commons-logging library has explicitly + * been granted permission for that operation. In this case, we need to + * run the operation using an AccessController. + */ + private static InputStream getResourceAsStream(final ClassLoader loader, + final String name) + { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + if (loader != null) { + return loader.getResourceAsStream(name); + } else { + return ClassLoader.getSystemResourceAsStream(name); + } + } + }); + } + + /** + * Given a filename, return an enumeration of URLs pointing to + * all the occurrences of that filename in the classpath. + * <p> + * This is just like ClassLoader.getResources except that the + * operation is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + * <p> + * If no instances are found, an Enumeration is returned whose + * hasMoreElements method returns false (ie an "empty" enumeration). + * If resources could not be listed for some reason, null is returned. + */ + private static Enumeration getResources(final ClassLoader loader, + final String name) + { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + try { + if (loader != null) { + return loader.getResources(name); + } else { + return ClassLoader.getSystemResources(name); + } + } catch(IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Exception while trying to find configuration file " + + name + ":" + e.getMessage()); + } + return null; + } catch(NoSuchMethodError e) { + // we must be running on a 1.1 JVM which doesn't support + // ClassLoader.getSystemResources; just return null in + // this case. + return null; + } + } + }; + Object result = AccessController.doPrivileged(action); + return (Enumeration) result; + } + + /** + * Given a URL that refers to a .properties file, load that file. + * This is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + * <p> + * Null is returned if the URL cannot be opened. + */ + private static Properties getProperties(final URL url) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + try { + InputStream stream = url.openStream(); + if (stream != null) { + Properties props = new Properties(); + props.load(stream); + stream.close(); + return props; + } + } catch(IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to read URL " + url); + } + } + + return null; + } + }; + return (Properties) AccessController.doPrivileged(action); + } + + /** + * Locate a user-provided configuration file. + * <p> + * The classpath of the specified classLoader (usually the context classloader) + * is searched for properties files of the specified name. If none is found, + * null is returned. If more than one is found, then the file with the greatest + * value for its PRIORITY property is returned. If multiple files have the + * same PRIORITY value then the first in the classpath is returned. + * <p> + * This differs from the 1.0.x releases; those always use the first one found. + * However as the priority is a new field, this change is backwards compatible. + * <p> + * The purpose of the priority field is to allow a webserver administrator to + * override logging settings in all webapps by placing a commons-logging.properties + * file in a shared classpath location with a priority > 0; this overrides any + * commons-logging.properties files without priorities which are in the + * webapps. Webapps can also use explicit priorities to override a configuration + * file in the shared classpath if needed. + */ + private static final Properties getConfigurationFile( + ClassLoader classLoader, String fileName) { + + Properties props = null; + double priority = 0.0; + URL propsUrl = null; + try { + Enumeration urls = getResources(classLoader, fileName); + + if (urls == null) { + return null; + } + + while (urls.hasMoreElements()) { + URL url = (URL) urls.nextElement(); + + Properties newProps = getProperties(url); + if (newProps != null) { + if (props == null) { + propsUrl = url; + props = newProps; + String priorityStr = props.getProperty(PRIORITY_KEY); + priority = 0.0; + if (priorityStr != null) { + priority = Double.parseDouble(priorityStr); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Properties file found at '" + url + "'" + + " with priority " + priority); + } + } else { + String newPriorityStr = newProps.getProperty(PRIORITY_KEY); + double newPriority = 0.0; + if (newPriorityStr != null) { + newPriority = Double.parseDouble(newPriorityStr); + } + + if (newPriority > priority) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " overrides file at '" + propsUrl + "'" + + " with priority " + priority); + } + + propsUrl = url; + props = newProps; + priority = newPriority; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " does not override file at '" + propsUrl + "'" + + " with priority " + priority); + } + } + } + + } + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("SecurityException thrown while trying to find/read config files."); + } + } + + if (isDiagnosticsEnabled()) { + if (props == null) { + logDiagnostic( + "[LOOKUP] No properties file of name '" + fileName + + "' found."); + } else { + logDiagnostic( + "[LOOKUP] Properties file of name '" + fileName + + "' found at '" + propsUrl + '"'); + } + } + + return props; + } + + /** + * Determines whether the user wants internal diagnostic output. If so, + * returns an appropriate writer object. Users can enable diagnostic + * output by setting the system property named {@link #DIAGNOSTICS_DEST_PROPERTY} to + * a filename, or the special values STDOUT or STDERR. + */ + private static void initDiagnostics() { + String dest; + try { + dest = System.getProperty(DIAGNOSTICS_DEST_PROPERTY); + if (dest == null) { + return; + } + } catch(SecurityException ex) { + // We must be running in some very secure environment. + // We just have to assume output is not wanted.. + return; + } + + if (dest.equals("STDOUT")) { + diagnosticsStream = System.out; + } else if (dest.equals("STDERR")) { + diagnosticsStream = System.err; + } else { + try { + // open the file in append mode + FileOutputStream fos = new FileOutputStream(dest, true); + diagnosticsStream = new PrintStream(fos); + } catch(IOException ex) { + // We should report this to the user - but how? + return; + } + } + + // In order to avoid confusion where multiple instances of JCL are + // being used via different classloaders within the same app, we + // ensure each logged message has a prefix of form + // [LogFactory from classloader OID] + // + // Note that this prefix should be kept consistent with that + // in LogFactoryImpl. However here we don't need to output info + // about the actual *instance* of LogFactory, as all methods that + // output diagnostics from this class are static. + String classLoaderName; + try { + ClassLoader classLoader = thisClassLoader; + if (thisClassLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(classLoader); + } + } catch(SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactory from " + classLoaderName + "] "; + } + + /** + * Indicates true if the user has enabled internal logging. + * <p> + * By the way, sorry for the incorrect grammar, but calling this method + * areDiagnosticsEnabled just isn't java beans style. + * + * @return true if calls to logDiagnostic will have any effect. + * @since 1.1 + */ + protected static boolean isDiagnosticsEnabled() { + return diagnosticsStream != null; + } + + /** + * Write the specified message to the internal logging destination. + * <p> + * Note that this method is private; concrete subclasses of this class + * should not call it because the diagnosticPrefix string this + * method puts in front of all its messages is LogFactory@...., + * while subclasses should put SomeSubClass@... + * <p> + * Subclasses should instead compute their own prefix, then call + * logRawDiagnostic. Note that calling isDiagnosticsEnabled is + * fine for subclasses. + * <p> + * Note that it is safe to call this method before initDiagnostics + * is called; any output will just be ignored (as isDiagnosticsEnabled + * will return false). + * + * @param msg is the diagnostic message to be output. + */ + private static final void logDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.print(diagnosticPrefix); + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Write the specified message to the internal logging destination. + * + * @param msg is the diagnostic message to be output. + * @since 1.1 + */ + protected static final void logRawDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Generate useful diagnostics regarding the classloader tree for + * the specified class. + * <p> + * As an example, if the specified class was loaded via a webapp's + * classloader, then you may get the following output: + * <pre> + * Class com.acme.Foo was loaded via classloader 11111 + * ClassLoader tree: 11111 -> 22222 (SYSTEM) -> 33333 -> BOOT + * </pre> + * <p> + * This method returns immediately if isDiagnosticsEnabled() + * returns false. + * + * @param clazz is the class whose classloader + tree are to be + * output. + */ + private static void logClassLoaderEnvironment(Class clazz) { + if (!isDiagnosticsEnabled()) { + return; + } + + try { + logDiagnostic("[ENV] Extension directories (java.ext.dir): " + System.getProperty("java.ext.dir")); + logDiagnostic("[ENV] Application classpath (java.class.path): " + System.getProperty("java.class.path")); + } catch(SecurityException ex) { + logDiagnostic("[ENV] Security setting prevent interrogation of system classpaths."); + } + + String className = clazz.getName(); + ClassLoader classLoader; + + try { + classLoader = getClassLoader(clazz); + } catch(SecurityException ex) { + // not much useful diagnostics we can print here! + logDiagnostic( + "[ENV] Security forbids determining the classloader for " + className); + return; + } + + logDiagnostic( + "[ENV] Class " + className + " was loaded via classloader " + + objectId(classLoader)); + logHierarchy("[ENV] Ancestry of classloader which loaded " + className + " is ", classLoader); + } + + /** + * Logs diagnostic messages about the given classloader + * and it's hierarchy. The prefix is prepended to the message + * and is intended to make it easier to understand the logs. + * @param prefix + * @param classLoader + */ + private static void logHierarchy(String prefix, ClassLoader classLoader) { + if (!isDiagnosticsEnabled()) { + return; + } + ClassLoader systemClassLoader; + if (classLoader != null) { + final String classLoaderString = classLoader.toString(); + logDiagnostic(prefix + objectId(classLoader) + " == '" + classLoaderString + "'"); + } + + try { + systemClassLoader = ClassLoader.getSystemClassLoader(); + } catch(SecurityException ex) { + logDiagnostic( + prefix + "Security forbids determining the system classloader."); + return; + } + if (classLoader != null) { + StringBuffer buf = new StringBuffer(prefix + "ClassLoader tree:"); + for(;;) { + buf.append(objectId(classLoader)); + if (classLoader == systemClassLoader) { + buf.append(" (SYSTEM) "); + } + + try { + classLoader = classLoader.getParent(); + } catch(SecurityException ex) { + buf.append(" --> SECRET"); + break; + } + + buf.append(" --> "); + if (classLoader == null) { + buf.append("BOOT"); + break; + } + } + logDiagnostic(buf.toString()); + } + } + + /** + * Returns a string that uniquely identifies the specified object, including + * its class. + * <p> + * The returned string is of form "classname@hashcode", ie is the same as + * the return value of the Object.toString() method, but works even when + * the specified object's class has overidden the toString method. + * + * @param o may be null. + * @return a string of form classname@hashcode, or "null" if param o is null. + * @since 1.1 + */ + public static String objectId(Object o) { + if (o == null) { + return "null"; + } else { + return o.getClass().getName() + "@" + System.identityHashCode(o); + } + } + + // ---------------------------------------------------------------------- + // Static initialiser block to perform initialisation at class load time. + // + // We can't do this in the class constructor, as there are many + // static methods on this class that can be called before any + // LogFactory instances are created, and they depend upon this + // stuff having been set up. + // + // Note that this block must come after any variable declarations used + // by any methods called from this block, as we want any static initialiser + // associated with the variable to run first. If static initialisers for + // variables run after this code, then (a) their value might be needed + // by methods called from here, and (b) they might *override* any value + // computed here! + // + // So the wisest thing to do is just to place this code at the very end + // of the class file. + // ---------------------------------------------------------------------- + + static { + // note: it's safe to call methods before initDiagnostics. + thisClassLoader = getClassLoader(LogFactory.class); + initDiagnostics(); + logClassLoaderEnvironment(LogFactory.class); + factories = createFactoryStore(); + if (isDiagnosticsEnabled()) { + logDiagnostic("BOOTSTRAP COMPLETED"); + } + } +} diff --git a/src/org/apache/commons/logging/LogSource.java b/src/org/apache/commons/logging/LogSource.java new file mode 100644 index 0000000..e3c0603 --- /dev/null +++ b/src/org/apache/commons/logging/LogSource.java @@ -0,0 +1,261 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.logging; + + +import java.lang.reflect.Constructor; +import java.util.Hashtable; + +import org.apache.commons.logging.impl.NoOpLog; + + +/** + * <p>Factory for creating {@link Log} instances. Applications should call + * the <code>makeNewLogInstance()</code> method to instantiate new instances + * of the configured {@link Log} implementation class.</p> + * + * <p>By default, calling <code>getInstance()</code> will use the following + * algorithm:</p> + * <ul> + * <li>If Log4J is available, return an instance of + * <code>org.apache.commons.logging.impl.Log4JLogger</code>.</li> + * <li>If JDK 1.4 or later is available, return an instance of + * <code>org.apache.commons.logging.impl.Jdk14Logger</code>.</li> + * <li>Otherwise, return an instance of + * <code>org.apache.commons.logging.impl.NoOpLog</code>.</li> + * </ul> + * + * <p>You can change the default behavior in one of two ways:</p> + * <ul> + * <li>On the startup command line, set the system property + * <code>org.apache.commons.logging.log</code> to the name of the + * <code>org.apache.commons.logging.Log</code> implementation class + * you want to use.</li> + * <li>At runtime, call <code>LogSource.setLogImplementation()</code>.</li> + * </ul> + * + * @deprecated Use {@link LogFactory} instead - The default factory + * implementation performs exactly the same algorithm as this class did + * + * @author Rod Waldhoff + * @version $Id: LogSource.java 155426 2005-02-26 13:10:49Z dirkv $ + */ +public class LogSource { + + // ------------------------------------------------------- Class Attributes + + static protected Hashtable logs = new Hashtable(); + + /** Is log4j available (in the current classpath) */ + static protected boolean log4jIsAvailable = false; + + /** Is JDK 1.4 logging available */ + static protected boolean jdk14IsAvailable = false; + + /** Constructor for current log class */ + static protected Constructor logImplctor = null; + + + // ----------------------------------------------------- Class Initializers + + static { + + // Is Log4J Available? + try { + if (null != Class.forName("org.apache.log4j.Logger")) { + log4jIsAvailable = true; + } else { + log4jIsAvailable = false; + } + } catch (Throwable t) { + log4jIsAvailable = false; + } + + // Is JDK 1.4 Logging Available? + try { + if ((null != Class.forName("java.util.logging.Logger")) && + (null != Class.forName("org.apache.commons.logging.impl.Jdk14Logger"))) { + jdk14IsAvailable = true; + } else { + jdk14IsAvailable = false; + } + } catch (Throwable t) { + jdk14IsAvailable = false; + } + + // Set the default Log implementation + String name = null; + try { + name = System.getProperty("org.apache.commons.logging.log"); + if (name == null) { + name = System.getProperty("org.apache.commons.logging.Log"); + } + } catch (Throwable t) { + } + if (name != null) { + try { + setLogImplementation(name); + } catch (Throwable t) { + try { + setLogImplementation + ("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + ; + } + } + } else { + try { + if (log4jIsAvailable) { + setLogImplementation + ("org.apache.commons.logging.impl.Log4JLogger"); + } else if (jdk14IsAvailable) { + setLogImplementation + ("org.apache.commons.logging.impl.Jdk14Logger"); + } else { + setLogImplementation + ("org.apache.commons.logging.impl.NoOpLog"); + } + } catch (Throwable t) { + try { + setLogImplementation + ("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + ; + } + } + } + + } + + + // ------------------------------------------------------------ Constructor + + + /** Don't allow others to create instances */ + private LogSource() { + } + + + // ---------------------------------------------------------- Class Methods + + + /** + * Set the log implementation/log implementation factory + * by the name of the class. The given class + * must implement {@link Log}, and provide a constructor that + * takes a single {@link String} argument (containing the name + * of the log). + */ + static public void setLogImplementation(String classname) throws + LinkageError, ExceptionInInitializerError, + NoSuchMethodException, SecurityException, + ClassNotFoundException { + try { + Class logclass = Class.forName(classname); + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } catch (Throwable t) { + logImplctor = null; + } + } + + + /** + * Set the log implementation/log implementation factory + * by class. The given class must implement {@link Log}, + * and provide a constructor that takes a single {@link String} + * argument (containing the name of the log). + */ + static public void setLogImplementation(Class logclass) throws + LinkageError, ExceptionInInitializerError, + NoSuchMethodException, SecurityException { + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } + + + /** Get a <code>Log</code> instance by class name */ + static public Log getInstance(String name) { + Log log = (Log) (logs.get(name)); + if (null == log) { + log = makeNewLogInstance(name); + logs.put(name, log); + } + return log; + } + + + /** Get a <code>Log</code> instance by class */ + static public Log getInstance(Class clazz) { + return getInstance(clazz.getName()); + } + + + /** + * Create a new {@link Log} implementation, based + * on the given <i>name</i>. + * <p> + * The specific {@link Log} implementation returned + * is determined by the value of the + * <tt>org.apache.commons.logging.log</tt> property. + * The value of <tt>org.apache.commons.logging.log</tt> may be set to + * the fully specified name of a class that implements + * the {@link Log} interface. This class must also + * have a public constructor that takes a single + * {@link String} argument (containing the <i>name</i> + * of the {@link Log} to be constructed. + * <p> + * When <tt>org.apache.commons.logging.log</tt> is not set, + * or when no corresponding class can be found, + * this method will return a Log4JLogger + * if the log4j Logger class is + * available in the {@link LogSource}'s classpath, or a + * Jdk14Logger if we are on a JDK 1.4 or later system, or + * NoOpLog if neither of the above conditions is true. + * + * @param name the log name (or category) + */ + static public Log makeNewLogInstance(String name) { + + Log log = null; + try { + Object[] args = new Object[1]; + args[0] = name; + log = (Log) (logImplctor.newInstance(args)); + } catch (Throwable t) { + log = null; + } + if (null == log) { + log = new NoOpLog(name); + } + return log; + + } + + + /** + * Returns a {@link String} array containing the names of + * all logs known to me. + */ + static public String[] getLogNames() { + return (String[]) (logs.keySet().toArray(new String[logs.size()])); + } + + +} diff --git a/src/org/apache/commons/logging/impl/Jdk14Logger.java b/src/org/apache/commons/logging/impl/Jdk14Logger.java new file mode 100644 index 0000000..d4f840c --- /dev/null +++ b/src/org/apache/commons/logging/impl/Jdk14Logger.java @@ -0,0 +1,303 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + + +import java.io.Serializable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.logging.Log; + + +/** + * <p>Implementation of the <code>org.apache.commons.logging.Log</code> + * interface that wraps the standard JDK logging mechanisms that were + * introduced in the Merlin release (JDK 1.4).</p> + * + * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> + * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a> + * @author <a href="mailto:donaldp@apache.org">Peter Donald</a> + * @version $Revision: 370652 $ $Date: 2006-01-19 22:23:48 +0000 (Thu, 19 Jan 2006) $ + */ + +public class Jdk14Logger implements Log, Serializable { + + /** + * This member variable simply ensures that any attempt to initialise + * this class in a pre-1.4 JVM will result in an ExceptionInInitializerError. + * It must not be private, as an optimising compiler could detect that it + * is not used and optimise it away. + */ + protected static final Level dummyLevel = Level.FINE; + + // ----------------------------------------------------------- Constructors + + + /** + * Construct a named instance of this Logger. + * + * @param name Name of the logger to be constructed + */ + public Jdk14Logger(String name) { + + this.name = name; + logger = getLogger(); + + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * The underlying Logger implementation we are using. + */ + protected transient Logger logger = null; + + + /** + * The name of the logger we are wrapping. + */ + protected String name = null; + + + // --------------------------------------------------------- Public Methods + + private void log( Level level, String msg, Throwable ex ) { + + Logger logger = getLogger(); + if (logger.isLoggable(level)) { + // Hack (?) to get the stack trace. + Throwable dummyException=new Throwable(); + StackTraceElement locations[]=dummyException.getStackTrace(); + // Caller will be the third element + String cname="unknown"; + String method="unknown"; + if( locations!=null && locations.length >2 ) { + StackTraceElement caller=locations[2]; + cname=caller.getClassName(); + method=caller.getMethodName(); + } + if( ex==null ) { + logger.logp( level, cname, method, msg ); + } else { + logger.logp( level, cname, method, msg, ex ); + } + } + + } + + /** + * Logs a message with <code>java.util.logging.Level.FINE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + log(Level.FINE, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.FINE</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable exception) { + log(Level.FINE, String.valueOf(message), exception); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + + /** + * Return the native Logger instance we are using. + */ + public Logger getLogger() { + if (logger == null) { + logger = Logger.getLogger(name); + } + return (logger); + } + + + /** + * Logs a message with <code>java.util.logging.Level.INFO</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + log(Level.INFO, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.INFO</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable exception) { + log(Level.INFO, String.valueOf(message), exception); + } + + + /** + * Is debug logging currently enabled? + */ + public boolean isDebugEnabled() { + return (getLogger().isLoggable(Level.FINE)); + } + + + /** + * Is error logging currently enabled? + */ + public boolean isErrorEnabled() { + return (getLogger().isLoggable(Level.SEVERE)); + } + + + /** + * Is fatal logging currently enabled? + */ + public boolean isFatalEnabled() { + return (getLogger().isLoggable(Level.SEVERE)); + } + + + /** + * Is info logging currently enabled? + */ + public boolean isInfoEnabled() { + return (getLogger().isLoggable(Level.INFO)); + } + + + /** + * Is trace logging currently enabled? + */ + public boolean isTraceEnabled() { + return (getLogger().isLoggable(Level.FINEST)); + } + + + /** + * Is warn logging currently enabled? + */ + public boolean isWarnEnabled() { + return (getLogger().isLoggable(Level.WARNING)); + } + + + /** + * Logs a message with <code>java.util.logging.Level.FINEST</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + log(Level.FINEST, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.FINEST</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable exception) { + log(Level.FINEST, String.valueOf(message), exception); + } + + + /** + * Logs a message with <code>java.util.logging.Level.WARNING</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + log(Level.WARNING, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.WARNING</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable exception) { + log(Level.WARNING, String.valueOf(message), exception); + } + + +} diff --git a/src/org/apache/commons/logging/impl/LogFactoryImpl.java b/src/org/apache/commons/logging/impl/LogFactoryImpl.java new file mode 100644 index 0000000..8937b2f --- /dev/null +++ b/src/org/apache/commons/logging/impl/LogFactoryImpl.java @@ -0,0 +1,1400 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.logging.impl; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; + + +/** + * <p>Concrete subclass of {@link LogFactory} that implements the + * following algorithm to dynamically select a logging implementation + * class to instantiate a wrapper for.</p> + * <ul> + * <li>Use a factory configuration attribute named + * <code>org.apache.commons.logging.Log</code> to identify the + * requested implementation class.</li> + * <li>Use the <code>org.apache.commons.logging.Log</code> system property + * to identify the requested implementation class.</li> + * <li>If <em>Log4J</em> is available, return an instance of + * <code>org.apache.commons.logging.impl.Log4JLogger</code>.</li> + * <li>If <em>JDK 1.4 or later</em> is available, return an instance of + * <code>org.apache.commons.logging.impl.Jdk14Logger</code>.</li> + * <li>Otherwise, return an instance of + * <code>org.apache.commons.logging.impl.SimpleLog</code>.</li> + * </ul> + * + * <p>If the selected {@link Log} implementation class has a + * <code>setLogFactory()</code> method that accepts a {@link LogFactory} + * parameter, this method will be called on each newly created instance + * to identify the associated factory. This makes factory configuration + * attributes available to the Log instance, if it so desires.</p> + * + * <p>This factory will remember previously created <code>Log</code> instances + * for the same name, and will return them on repeated requests to the + * <code>getInstance()</code> method.</p> + * + * @author Rod Waldhoff + * @author Craig R. McClanahan + * @author Richard A. Sitze + * @author Brian Stansberry + * @version $Revision: 399224 $ $Date: 2006-05-03 10:25:54 +0100 (Wed, 03 May 2006) $ + */ + +public class LogFactoryImpl extends LogFactory { + + + /** Log4JLogger class name */ + private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger"; + /** Jdk14Logger class name */ + private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger"; + /** Jdk13LumberjackLogger class name */ + private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger"; + /** SimpleLog class name */ + private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog"; + + private static final String PKG_IMPL="org.apache.commons.logging.impl."; + private static final int PKG_LEN = PKG_IMPL.length(); + + // ----------------------------------------------------------- Constructors + + + + /** + * Public no-arguments constructor required by the lookup mechanism. + */ + public LogFactoryImpl() { + super(); + initDiagnostics(); // method on this object + if (isDiagnosticsEnabled()) { + logDiagnostic("Instance created."); + } + } + + + // ----------------------------------------------------- Manifest Constants + + + /** + * The name (<code>org.apache.commons.logging.Log</code>) of the system + * property identifying our {@link Log} implementation class. + */ + public static final String LOG_PROPERTY = + "org.apache.commons.logging.Log"; + + + /** + * The deprecated system property used for backwards compatibility with + * old versions of JCL. + */ + protected static final String LOG_PROPERTY_OLD = + "org.apache.commons.logging.log"; + + /** + * The name (<code>org.apache.commons.logging.Log.allowFlawedContext</code>) + * of the system property which can be set true/false to + * determine system behaviour when a bad context-classloader is encountered. + * When set to false, a LogConfigurationException is thrown if + * LogFactoryImpl is loaded via a child classloader of the TCCL (this + * should never happen in sane systems). + * + * Default behaviour: true (tolerates bad context classloaders) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_CONTEXT_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedContext"; + + /** + * The name (<code>org.apache.commons.logging.Log.allowFlawedDiscovery</code>) + * of the system property which can be set true/false to + * determine system behaviour when a bad logging adapter class is + * encountered during logging discovery. When set to false, an + * exception will be thrown and the app will fail to start. When set + * to true, discovery will continue (though the user might end up + * with a different logging implementation than they expected). + * + * Default behaviour: true (tolerates bad logging adapters) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_DISCOVERY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedDiscovery"; + + /** + * The name (<code>org.apache.commons.logging.Log.allowFlawedHierarchy</code>) + * of the system property which can be set true/false to + * determine system behaviour when a logging adapter class is + * encountered which has bound to the wrong Log class implementation. + * When set to false, an exception will be thrown and the app will fail + * to start. When set to true, discovery will continue (though the user + * might end up with a different logging implementation than they expected). + * + * Default behaviour: true (tolerates bad Log class hierarchy) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_HIERARCHY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedHierarchy"; + + + /** + * The names of classes that will be tried (in order) as logging + * adapters. Each class is expected to implement the Log interface, + * and to throw NoClassDefFound or ExceptionInInitializerError when + * loaded if the underlying logging library is not available. Any + * other error indicates that the underlying logging library is available + * but broken/unusable for some reason. + */ + private static final String[] classesToDiscover = { + LOGGING_IMPL_LOG4J_LOGGER, + "org.apache.commons.logging.impl.Jdk14Logger", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger", + "org.apache.commons.logging.impl.SimpleLog" + }; + + + // ----------------------------------------------------- Instance Variables + + /** + * Determines whether logging classes should be loaded using the thread-context + * classloader, or via the classloader that loaded this LogFactoryImpl class. + */ + private boolean useTCCL = true; + + /** + * The string prefixed to every message output by the logDiagnostic method. + */ + private String diagnosticPrefix; + + + /** + * Configuration attributes. + */ + protected Hashtable attributes = new Hashtable(); + + + /** + * The {@link org.apache.commons.logging.Log} instances that have + * already been created, keyed by logger name. + */ + protected Hashtable instances = new Hashtable(); + + + /** + * Name of the class implementing the Log interface. + */ + private String logClassName; + + + /** + * The one-argument constructor of the + * {@link org.apache.commons.logging.Log} + * implementation class that will be used to create new instances. + * This value is initialized by <code>getLogConstructor()</code>, + * and then returned repeatedly. + */ + protected Constructor logConstructor = null; + + + /** + * The signature of the Constructor to be used. + */ + protected Class logConstructorSignature[] = + { java.lang.String.class }; + + + /** + * The one-argument <code>setLogFactory</code> method of the selected + * {@link org.apache.commons.logging.Log} method, if it exists. + */ + protected Method logMethod = null; + + + /** + * The signature of the <code>setLogFactory</code> method to be used. + */ + protected Class logMethodSignature[] = + { LogFactory.class }; + + /** + * See getBaseClassLoader and initConfiguration. + */ + private boolean allowFlawedContext; + + /** + * See handleFlawedDiscovery and initConfiguration. + */ + private boolean allowFlawedDiscovery; + + /** + * See handleFlawedHierarchy and initConfiguration. + */ + private boolean allowFlawedHierarchy; + + // --------------------------------------------------------- Public Methods + + + /** + * Return the configuration attribute with the specified name (if any), + * or <code>null</code> if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public Object getAttribute(String name) { + + return (attributes.get(name)); + + } + + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public String[] getAttributeNames() { + + Vector names = new Vector(); + Enumeration keys = attributes.keys(); + while (keys.hasMoreElements()) { + names.addElement((String) keys.nextElement()); + } + String results[] = new String[names.size()]; + for (int i = 0; i < results.length; i++) { + results[i] = (String) names.elementAt(i); + } + return (results); + + } + + + /** + * Convenience method to derive a name from the specified class and + * call <code>getInstance(String)</code> with it. + * + * @param clazz Class for which a suitable Log name will be derived + * + * @exception LogConfigurationException if a suitable <code>Log</code> + * instance cannot be returned + */ + public Log getInstance(Class clazz) throws LogConfigurationException { + + return (getInstance(clazz.getName())); + + } + + + /** + * <p>Construct (if necessary) and return a <code>Log</code> instance, + * using the factory's current set of configuration attributes.</p> + * + * <p><strong>NOTE</strong> - Depending upon the implementation of + * the <code>LogFactory</code> you are using, the <code>Log</code> + * instance you are returned may or may not be local to the current + * application, and may or may not be returned again on a subsequent + * call with the same name argument.</p> + * + * @param name Logical name of the <code>Log</code> instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable <code>Log</code> + * instance cannot be returned + */ + public Log getInstance(String name) throws LogConfigurationException { + + Log instance = (Log) instances.get(name); + if (instance == null) { + instance = newInstance(name); + instances.put(name, instance); + } + return (instance); + + } + + + /** + * Release any internal references to previously created + * {@link org.apache.commons.logging.Log} + * instances returned by this factory. This is useful in environments + * like servlet containers, which implement application reloading by + * throwing away a ClassLoader. Dangling references to objects in that + * class loader would prevent garbage collection. + */ + public void release() { + + logDiagnostic("Releasing all known loggers"); + instances.clear(); + } + + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public void removeAttribute(String name) { + + attributes.remove(name); + + } + + + /** + * Set the configuration attribute with the specified name. Calling + * this with a <code>null</code> value is equivalent to calling + * <code>removeAttribute(name)</code>. + * <p> + * This method can be used to set logging configuration programmatically + * rather than via system properties. It can also be used in code running + * within a container (such as a webapp) to configure behaviour on a + * per-component level instead of globally as system properties would do. + * To use this method instead of a system property, call + * <pre> + * LogFactory.getFactory().setAttribute(...) + * </pre> + * This must be done before the first Log object is created; configuration + * changes after that point will be ignored. + * <p> + * This method is also called automatically if LogFactory detects a + * commons-logging.properties file; every entry in that file is set + * automatically as an attribute here. + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or <code>null</code> + * to remove any setting for this attribute + */ + public void setAttribute(String name, Object value) { + + if (logConstructor != null) { + logDiagnostic("setAttribute: call too late; configuration already performed."); + } + + if (value == null) { + attributes.remove(name); + } else { + attributes.put(name, value); + } + + if (name.equals(TCCL_KEY)) { + useTCCL = Boolean.valueOf(value.toString()).booleanValue(); + } + + } + + + // ------------------------------------------------------ + // Static Methods + // + // These methods only defined as workarounds for a java 1.2 bug; + // theoretically none of these are needed. + // ------------------------------------------------------ + + /** + * Gets the context classloader. + * This method is a workaround for a java 1.2 compiler bug. + * @since 1.1 + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return LogFactory.getContextClassLoader(); + } + + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.isDiagnosticsEnabled. + */ + protected static boolean isDiagnosticsEnabled() { + return LogFactory.isDiagnosticsEnabled(); + } + + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.getClassLoader. + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + return LogFactory.getClassLoader(clazz); + } + + + // ------------------------------------------------------ Protected Methods + + /** + * Calculate and cache a string that uniquely identifies this instance, + * including which classloader the object was loaded from. + * <p> + * This string will later be prefixed to each "internal logging" message + * emitted, so that users can clearly see any unexpected behaviour. + * <p> + * Note that this method does not detect whether internal logging is + * enabled or not, nor where to output stuff if it is; that is all + * handled by the parent LogFactory class. This method just computes + * its own unique prefix for log messages. + */ + private void initDiagnostics() { + // It would be nice to include an identifier of the context classloader + // that this LogFactoryImpl object is responsible for. However that + // isn't possible as that information isn't available. It is possible + // to figure this out by looking at the logging from LogFactory to + // see the context & impl ids from when this object was instantiated, + // in order to link the impl id output as this object's prefix back to + // the context it is intended to manage. + // Note that this prefix should be kept consistent with that + // in LogFactory. + Class clazz = this.getClass(); + ClassLoader classLoader = getClassLoader(clazz); + String classLoaderName; + try { + if (classLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(classLoader); + } + } catch(SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] "; + } + + + /** + * Output a diagnostic message to a user-specified destination (if the + * user has enabled diagnostic logging). + * + * @param msg diagnostic message + * @since 1.1 + */ + protected void logDiagnostic(String msg) { + if (isDiagnosticsEnabled()) { + logRawDiagnostic(diagnosticPrefix + msg); + } + } + + /** + * Return the fully qualified Java classname of the {@link Log} + * implementation we will be using. + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected String getLogClassName() { + + if (logClassName == null) { + discoverLogImplementation(getClass().getName()); + } + + return logClassName; + } + + + /** + * <p>Return the <code>Constructor</code> that can be called to instantiate + * new {@link org.apache.commons.logging.Log} instances.</p> + * + * <p><strong>IMPLEMENTATION NOTE</strong> - Race conditions caused by + * calling this method from more than one thread are ignored, because + * the same <code>Constructor</code> instance will ultimately be derived + * in all circumstances.</p> + * + * @exception LogConfigurationException if a suitable constructor + * cannot be returned + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected Constructor getLogConstructor() + throws LogConfigurationException { + + // Return the previously identified Constructor (if any) + if (logConstructor == null) { + discoverLogImplementation(getClass().getName()); + } + + return logConstructor; + } + + + /** + * Is <em>JDK 1.3 with Lumberjack</em> logging available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk13LumberjackAvailable() { + return isLogLibraryAvailable( + "Jdk13Lumberjack", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); + } + + + /** + * <p>Return <code>true</code> if <em>JDK 1.4 or later</em> logging + * is available. Also checks that the <code>Throwable</code> class + * supports <code>getStackTrace()</code>, which is required by + * Jdk14Logger.</p> + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk14Available() { + return isLogLibraryAvailable( + "Jdk14", + "org.apache.commons.logging.impl.Jdk14Logger"); + } + + + /** + * Is a <em>Log4J</em> implementation available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isLog4JAvailable() { + return isLogLibraryAvailable( + "Log4J", + LOGGING_IMPL_LOG4J_LOGGER); + } + + + /** + * Create and return a new {@link org.apache.commons.logging.Log} + * instance for the specified name. + * + * @param name Name of the new logger + * + * @exception LogConfigurationException if a new instance cannot + * be created + */ + protected Log newInstance(String name) throws LogConfigurationException { + + Log instance = null; + try { + if (logConstructor == null) { + instance = discoverLogImplementation(name); + } + else { + Object params[] = { name }; + instance = (Log) logConstructor.newInstance(params); + } + + if (logMethod != null) { + Object params[] = { this }; + logMethod.invoke(instance, params); + } + + return (instance); + + } catch (LogConfigurationException lce) { + + // this type of exception means there was a problem in discovery + // and we've already output diagnostics about the issue, etc.; + // just pass it on + throw (LogConfigurationException) lce; + + } catch (InvocationTargetException e) { + // A problem occurred invoking the Constructor or Method + // previously discovered + Throwable c = e.getTargetException(); + if (c != null) { + throw new LogConfigurationException(c); + } else { + throw new LogConfigurationException(e); + } + } catch (Throwable t) { + // A problem occurred invoking the Constructor or Method + // previously discovered + throw new LogConfigurationException(t); + } + } + + + // ------------------------------------------------------ Private Methods + + /** + * Utility method to check whether a particular logging library is + * present and available for use. Note that this does <i>not</i> + * affect the future behaviour of this class. + */ + private boolean isLogLibraryAvailable(String name, String classname) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Checking for '" + name + "'."); + } + try { + Log log = createLogFromClass( + classname, + this.getClass().getName(), // dummy category + false); + + if (log == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Did not find '" + name + "'."); + } + return false; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("Found '" + name + "'."); + } + return true; + } + } catch(LogConfigurationException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Logging system '" + name + "' is available but not useable."); + } + return false; + } + } + + /** + * Attempt to find an attribute (see method setAttribute) or a + * system property with the provided name and return its value. + * <p> + * The attributes associated with this object are checked before + * system properties in case someone has explicitly called setAttribute, + * or a configuration property has been set in a commons-logging.properties + * file. + * + * @return the value associated with the property, or null. + */ + private String getConfigurationValue(String property) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Trying to get configuration for item " + property); + } + + Object valueObj = getAttribute(property); + if (valueObj != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property); + } + return valueObj.toString(); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No LogFactory attribute found for " + property); + } + + try { + String value = System.getProperty(property); + if (value != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found system property [" + value + "] for " + property); + } + return value; + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No system property found for property " + property); + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Security prevented reading system property " + property); + } + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No configuration defined for item " + property); + } + + return null; + } + + /** + * Get the setting for the user-configurable behaviour specified by key. + * If nothing has explicitly been set, then return dflt. + */ + private boolean getBooleanConfiguration(String key, boolean dflt) { + String val = getConfigurationValue(key); + if (val == null) + return dflt; + return Boolean.valueOf(val).booleanValue(); + } + + /** + * Initialize a number of variables that control the behaviour of this + * class and that can be tweaked by the user. This is done when the first + * logger is created, not in the constructor of this class, because we + * need to give the user a chance to call method setAttribute in order to + * configure this object. + */ + private void initConfiguration() { + allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true); + allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true); + allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true); + } + + + /** + * Attempts to create a Log instance for the given category name. + * Follows the discovery process described in the class javadoc. + * + * @param logCategory the name of the log category + * + * @throws LogConfigurationException if an error in discovery occurs, + * or if no adapter at all can be instantiated + */ + private Log discoverLogImplementation(String logCategory) + throws LogConfigurationException + { + if (isDiagnosticsEnabled()) { + logDiagnostic("Discovering a Log implementation..."); + } + + initConfiguration(); + + Log result = null; + + // See if the user specified the Log implementation to use + String specifiedLogClassName = findUserSpecifiedLogClassName(); + + if (specifiedLogClassName != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to load user-specified log class '" + + specifiedLogClassName + "'..."); + } + + result = createLogFromClass(specifiedLogClassName, + logCategory, + true); + if (result == null) { + StringBuffer messageBuffer = new StringBuffer("User-specified log class '"); + messageBuffer.append(specifiedLogClassName); + messageBuffer.append("' cannot be found or is not useable."); + + // Mistyping or misspelling names is a common fault. + // Construct a good error message, if we can + if (specifiedLogClassName != null) { + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); + } + throw new LogConfigurationException(messageBuffer.toString()); + } + + return result; + } + + // No user specified log; try to discover what's on the classpath + // + // Note that we deliberately loop here over classesToDiscover and + // expect method createLogFromClass to loop over the possible source + // classloaders. The effect is: + // for each discoverable log adapter + // for each possible classloader + // see if it works + // + // It appears reasonable at first glance to do the opposite: + // for each possible classloader + // for each discoverable log adapter + // see if it works + // + // The latter certainly has advantages for user-installable logging + // libraries such as log4j; in a webapp for example this code should + // first check whether the user has provided any of the possible + // logging libraries before looking in the parent classloader. + // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, + // and SimpleLog will always work in any JVM. So the loop would never + // ever look for logging libraries in the parent classpath. Yet many + // users would expect that putting log4j there would cause it to be + // detected (and this is the historical JCL behaviour). So we go with + // the first approach. A user that has bundled a specific logging lib + // in a webapp should use a commons-logging.properties file or a + // service file in META-INF to force use of that logging lib anyway, + // rather than relying on discovery. + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "No user-specified Log implementation; performing discovery" + + " using the standard supported logging implementations..."); + } + for(int i=0; (i<classesToDiscover.length) && (result == null); ++i) { + result = createLogFromClass(classesToDiscover[i], logCategory, true); + } + + if (result == null) { + throw new LogConfigurationException + ("No suitable Log implementation"); + } + + return result; + } + + + /** + * Appends message if the given name is similar to the candidate. + * @param messageBuffer <code>StringBuffer</code> the message should be appended to, + * not null + * @param name the (trimmed) name to be test against the candidate, not null + * @param candidate the candidate name (not null) + */ + private void informUponSimilarName(final StringBuffer messageBuffer, final String name, + final String candidate) { + if (name.equals(candidate)) { + // Don't suggest a name that is exactly the same as the one the + // user tried... + return; + } + + // If the user provides a name that is in the right package, and gets + // the first 5 characters of the adapter class right (ignoring case), + // then suggest the candidate adapter class name. + if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) { + messageBuffer.append(" Did you mean '"); + messageBuffer.append(candidate); + messageBuffer.append("'?"); + } + } + + + /** + * Checks system properties and the attribute map for + * a Log implementation specified by the user under the + * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}. + * + * @return classname specified by the user, or <code>null</code> + */ + private String findUserSpecifiedLogClassName() + { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'"); + } + String specifiedClass = (String) getAttribute(LOG_PROPERTY); + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + + LOG_PROPERTY_OLD + "'"); + } + specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); + } + + if (specifiedClass == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY + "'"); + } + try { + specifiedClass = System.getProperty(LOG_PROPERTY); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY + "' - " + e.getMessage()); + } + } + } + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY_OLD + "'"); + } + try { + specifiedClass = System.getProperty(LOG_PROPERTY_OLD); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY_OLD + "' - " + e.getMessage()); + } + } + } + + // Remove any whitespace; it's never valid in a classname so its + // presence just means a user mistake. As we know what they meant, + // we may as well strip the spaces. + if (specifiedClass != null) { + specifiedClass = specifiedClass.trim(); + } + + return specifiedClass; + } + + + /** + * Attempts to load the given class, find a suitable constructor, + * and instantiate an instance of Log. + * + * @param logAdapterClassName classname of the Log implementation + * + * @param logCategory argument to pass to the Log implementation's + * constructor + * + * @param affectState <code>true</code> if this object's state should + * be affected by this method call, <code>false</code> otherwise. + * + * @return an instance of the given class, or null if the logging + * library associated with the specified adapter is not available. + * + * @throws LogConfigurationException if there was a serious error with + * configuration and the handleFlawedDiscovery method decided this + * problem was fatal. + */ + private Log createLogFromClass(String logAdapterClassName, + String logCategory, + boolean affectState) + throws LogConfigurationException { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'"); + } + + Object[] params = { logCategory }; + Log logAdapter = null; + Constructor constructor = null; + + Class logAdapterClass = null; + ClassLoader currentCL = getBaseClassLoader(); + + for(;;) { + // Loop through the classloader hierarchy trying to find + // a viable classloader. + logDiagnostic( + "Trying to load '" + + logAdapterClassName + + "' from classloader " + + objectId(currentCL)); + try { + if (isDiagnosticsEnabled()) { + // Show the location of the first occurrence of the .class file + // in the classpath. This is the location that ClassLoader.loadClass + // will load the class from -- unless the classloader is doing + // something weird. + URL url; + String resourceName = logAdapterClassName.replace('.', '/') + ".class"; + if (currentCL != null) { + url = currentCL.getResource(resourceName ); + } else { + url = ClassLoader.getSystemResource(resourceName + ".class"); + } + + if (url == null) { + logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found."); + } else { + logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'"); + } + } + + Class c = null; + try { + c = Class.forName(logAdapterClassName, true, currentCL); + } catch (ClassNotFoundException originalClassNotFoundException) { + // The current classloader was unable to find the log adapter + // in this or any ancestor classloader. There's no point in + // trying higher up in the hierarchy in this case.. + String msg = "" + originalClassNotFoundException.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is not available via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + try { + // Try the class classloader. + // This may work in cases where the TCCL + // does not contain the code executed or JCL. + // This behaviour indicates that the application + // classloading strategy is not consistent with the + // Java 1.2 classloading guidelines but JCL can + // and so should handle this case. + c = Class.forName(logAdapterClassName); + } catch (ClassNotFoundException secondaryClassNotFoundException) { + // no point continuing: this adapter isn't available + msg = "" + secondaryClassNotFoundException.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is not available via the LogFactoryImpl class classloader: " + + msg.trim()); + break; + } + } + + constructor = c.getConstructor(logConstructorSignature); + Object o = constructor.newInstance(params); + + // Note that we do this test after trying to create an instance + // [rather than testing Log.class.isAssignableFrom(c)] so that + // we don't complain about Log hierarchy problems when the + // adapter couldn't be instantiated anyway. + if (o instanceof Log) { + logAdapterClass = c; + logAdapter = (Log) o; + break; + } + + // Oops, we have a potential problem here. An adapter class + // has been found and its underlying lib is present too, but + // there are multiple Log interface classes available making it + // impossible to cast to the type the caller wanted. We + // certainly can't use this logger, but we need to know whether + // to keep on discovering or terminate now. + // + // The handleFlawedHierarchy method will throw + // LogConfigurationException if it regards this problem as + // fatal, and just return if not. + handleFlawedHierarchy(currentCL, c); + } catch (NoClassDefFoundError e) { + // We were able to load the adapter but it had references to + // other classes that could not be found. This simply means that + // the underlying logger library is not present in this or any + // ancestor classloader. There's no point in trying higher up + // in the hierarchy in this case.. + String msg = "" + e.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is missing dependencies when loaded via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + break; + } catch (ExceptionInInitializerError e) { + // A static initializer block or the initializer code associated + // with a static variable on the log adapter class has thrown + // an exception. + // + // We treat this as meaning the adapter's underlying logging + // library could not be found. + String msg = "" + e.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is unable to initialize itself when loaded via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + break; + } catch(LogConfigurationException e) { + // call to handleFlawedHierarchy above must have thrown + // a LogConfigurationException, so just throw it on + throw e; + } catch(Throwable t) { + // handleFlawedDiscovery will determine whether this is a fatal + // problem or not. If it is fatal, then a LogConfigurationException + // will be thrown. + handleFlawedDiscovery(logAdapterClassName, currentCL, t); + } + + if (currentCL == null) { + break; + } + + // try the parent classloader + currentCL = currentCL.getParent(); + } + + if ((logAdapter != null) && affectState) { + // We've succeeded, so set instance fields + this.logClassName = logAdapterClassName; + this.logConstructor = constructor; + + // Identify the <code>setLogFactory</code> method (if there is one) + try { + this.logMethod = logAdapterClass.getMethod("setLogFactory", + logMethodSignature); + logDiagnostic("Found method setLogFactory(LogFactory) in '" + + logAdapterClassName + "'"); + } catch (Throwable t) { + this.logMethod = null; + logDiagnostic( + "[INFO] '" + logAdapterClassName + + "' from classloader " + objectId(currentCL) + + " does not declare optional method " + + "setLogFactory(LogFactory)"); + } + + logDiagnostic( + "Log adapter '" + logAdapterClassName + + "' from classloader " + objectId(logAdapterClass.getClassLoader()) + + " has been selected for use."); + } + + return logAdapter; + } + + + /** + * Return the classloader from which we should try to load the logging + * adapter classes. + * <p> + * This method usually returns the context classloader. However if it + * is discovered that the classloader which loaded this class is a child + * of the context classloader <i>and</i> the allowFlawedContext option + * has been set then the classloader which loaded this class is returned + * instead. + * <p> + * The only time when the classloader which loaded this class is a + * descendant (rather than the same as or an ancestor of the context + * classloader) is when an app has created custom classloaders but + * failed to correctly set the context classloader. This is a bug in + * the calling application; however we provide the option for JCL to + * simply generate a warning rather than fail outright. + * + */ + private ClassLoader getBaseClassLoader() throws LogConfigurationException { + ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class); + + if (useTCCL == false) { + return thisClassLoader; + } + + ClassLoader contextClassLoader = getContextClassLoader(); + + ClassLoader baseClassLoader = getLowestClassLoader( + contextClassLoader, thisClassLoader); + + if (baseClassLoader == null) { + // The two classloaders are not part of a parent child relationship. + // In some classloading setups (e.g. JBoss with its + // UnifiedLoaderRepository) this can still work, so if user hasn't + // forbidden it, just return the contextClassLoader. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[WARNING] the context classloader is not part of a" + + " parent-child relationship with the classloader that" + + " loaded LogFactoryImpl."); + } + // If contextClassLoader were null, getLowestClassLoader() would + // have returned thisClassLoader. The fact we are here means + // contextClassLoader is not null, so we can just return it. + return contextClassLoader; + } + else { + throw new LogConfigurationException( + "Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + if (baseClassLoader != contextClassLoader) { + // We really should just use the contextClassLoader as the starting + // point for scanning for log adapter classes. However it is expected + // that there are a number of broken systems out there which create + // custom classloaders but fail to set the context classloader so + // we handle those flawed systems anyway. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Warning: the context classloader is an ancestor of the" + + " classloader that loaded LogFactoryImpl; it should be" + + " the same or a descendant. The application using" + + " commons-logging should ensure the context classloader" + + " is used correctly."); + } + } else { + throw new LogConfigurationException( + "Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + return baseClassLoader; + } + + /** + * Given two related classloaders, return the one which is a child of + * the other. + * <p> + * @param c1 is a classloader (including the null classloader) + * @param c2 is a classloader (including the null classloader) + * + * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor, + * and null if neither is an ancestor of the other. + */ + private ClassLoader getLowestClassLoader(ClassLoader c1, ClassLoader c2) { + // TODO: use AccessController when dealing with classloaders here + + if (c1 == null) + return c2; + + if (c2 == null) + return c1; + + ClassLoader current; + + // scan c1's ancestors to find c2 + current = c1; + while (current != null) { + if (current == c2) + return c1; + current = current.getParent(); + } + + // scan c2's ancestors to find c1 + current = c2; + while (current != null) { + if (current == c1) + return c2; + current = current.getParent(); + } + + return null; + } + + /** + * Generates an internal diagnostic logging of the discovery failure and + * then throws a <code>LogConfigurationException</code> that wraps + * the passed <code>Throwable</code>. + * + * @param logAdapterClassName is the class name of the Log implementation + * that could not be instantiated. Cannot be <code>null</code>. + * + * @param classLoader is the classloader that we were trying to load the + * logAdapterClassName from when the exception occurred. + * + * @param discoveryFlaw is the Throwable created by the classloader + * + * @throws LogConfigurationException ALWAYS + */ + private void handleFlawedDiscovery(String logAdapterClassName, + ClassLoader classLoader, + Throwable discoveryFlaw) { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Could not instantiate Log '" + + logAdapterClassName + "' -- " + + discoveryFlaw.getClass().getName() + ": " + + discoveryFlaw.getLocalizedMessage()); + } + + if (!allowFlawedDiscovery) { + throw new LogConfigurationException(discoveryFlaw); + } + } + + + /** + * Report a problem loading the log adapter, then either return + * (if the situation is considered recoverable) or throw a + * LogConfigurationException. + * <p> + * There are two possible reasons why we successfully loaded the + * specified log adapter class then failed to cast it to a Log object: + * <ol> + * <li>the specific class just doesn't implement the Log interface + * (user screwed up), or + * <li> the specified class has bound to a Log class loaded by some other + * classloader; Log@classloaderX cannot be cast to Log@classloaderY. + * </ol> + * <p> + * Here we try to figure out which case has occurred so we can give the + * user some reasonable feedback. + * + * @param badClassLoader is the classloader we loaded the problem class from, + * ie it is equivalent to badClass.getClassLoader(). + * + * @param badClass is a Class object with the desired name, but which + * does not implement Log correctly. + * + * @throws LogConfigurationException when the situation + * should not be recovered from. + */ + private void handleFlawedHierarchy(ClassLoader badClassLoader, Class badClass) + throws LogConfigurationException { + + boolean implementsLog = false; + String logInterfaceName = Log.class.getName(); + Class interfaces[] = badClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + if (logInterfaceName.equals(interfaces[i].getName())) { + implementsLog = true; + break; + } + } + + if (implementsLog) { + // the class does implement an interface called Log, but + // it is in the wrong classloader + if (isDiagnosticsEnabled()) { + try { + ClassLoader logInterfaceClassLoader = getClassLoader(Log.class); + logDiagnostic( + "Class '" + badClass.getName() + + "' was found in classloader " + + objectId(badClassLoader) + + ". It is bound to a Log interface which is not" + + " the one loaded from classloader " + + objectId(logInterfaceClassLoader)); + } catch (Throwable t) { + logDiagnostic( + "Error while trying to output diagnostics about" + + " bad class '" + badClass + "'"); + } + } + + if (!allowFlawedHierarchy) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context "); + msg.append("due to bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("Warning: bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + logDiagnostic(msg.toString()); + } + } else { + // this is just a bad adapter class + if (!allowFlawedDiscovery) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context. "); + msg.append("Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("[WARNING] Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + logDiagnostic(msg.toString()); + } + } + } +} diff --git a/src/org/apache/commons/logging/impl/NoOpLog.java b/src/org/apache/commons/logging/impl/NoOpLog.java new file mode 100644 index 0000000..b698813 --- /dev/null +++ b/src/org/apache/commons/logging/impl/NoOpLog.java @@ -0,0 +1,106 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + + +import java.io.Serializable; +import org.apache.commons.logging.Log; + + +/** + * <p>Trivial implementation of Log that throws away all messages. No + * configurable system properties are supported.</p> + * + * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> + * @author Rod Waldhoff + * @version $Id: NoOpLog.java 155426 2005-02-26 13:10:49Z dirkv $ + */ +public class NoOpLog implements Log, Serializable { + + /** Convenience constructor */ + public NoOpLog() { } + /** Base constructor */ + public NoOpLog(String name) { } + /** Do nothing */ + public void trace(Object message) { } + /** Do nothing */ + public void trace(Object message, Throwable t) { } + /** Do nothing */ + public void debug(Object message) { } + /** Do nothing */ + public void debug(Object message, Throwable t) { } + /** Do nothing */ + public void info(Object message) { } + /** Do nothing */ + public void info(Object message, Throwable t) { } + /** Do nothing */ + public void warn(Object message) { } + /** Do nothing */ + public void warn(Object message, Throwable t) { } + /** Do nothing */ + public void error(Object message) { } + /** Do nothing */ + public void error(Object message, Throwable t) { } + /** Do nothing */ + public void fatal(Object message) { } + /** Do nothing */ + public void fatal(Object message, Throwable t) { } + + /** + * Debug is never enabled. + * + * @return false + */ + public final boolean isDebugEnabled() { return false; } + + /** + * Error is never enabled. + * + * @return false + */ + public final boolean isErrorEnabled() { return false; } + + /** + * Fatal is never enabled. + * + * @return false + */ + public final boolean isFatalEnabled() { return false; } + + /** + * Info is never enabled. + * + * @return false + */ + public final boolean isInfoEnabled() { return false; } + + /** + * Trace is never enabled. + * + * @return false + */ + public final boolean isTraceEnabled() { return false; } + + /** + * Warn is never enabled. + * + * @return false + */ + public final boolean isWarnEnabled() { return false; } + +} diff --git a/src/org/apache/commons/logging/impl/SimpleLog.java b/src/org/apache/commons/logging/impl/SimpleLog.java new file mode 100644 index 0000000..6b643d3 --- /dev/null +++ b/src/org/apache/commons/logging/impl/SimpleLog.java @@ -0,0 +1,709 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + +import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; + +/** + * <p>Simple implementation of Log that sends all enabled log messages, + * for all defined loggers, to System.err. The following system properties + * are supported to configure the behavior of this logger:</p> + * <ul> + * <li><code>org.apache.commons.logging.simplelog.defaultlog</code> - + * Default logging detail level for all instances of SimpleLog. + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, defaults to "info". </li> + * <li><code>org.apache.commons.logging.simplelog.log.xxxxx</code> - + * Logging detail level for a SimpleLog instance named "xxxxx". + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, the default logging detail level is used.</li> + * <li><code>org.apache.commons.logging.simplelog.showlogname</code> - + * Set to <code>true</code> if you want the Log instance name to be + * included in output messages. Defaults to <code>false</code>.</li> + * <li><code>org.apache.commons.logging.simplelog.showShortLogname</code> - + * Set to <code>true</code> if you want the last component of the name to be + * included in output messages. Defaults to <code>true</code>.</li> + * <li><code>org.apache.commons.logging.simplelog.showdatetime</code> - + * Set to <code>true</code> if you want the current date and time + * to be included in output messages. Default is <code>false</code>.</li> + * <li><code>org.apache.commons.logging.simplelog.dateTimeFormat</code> - + * The date and time format to be used in the output messages. + * The pattern describing the date and time format is the same that is + * used in <code>java.text.SimpleDateFormat</code>. If the format is not + * specified or is invalid, the default format is used. + * The default format is <code>yyyy/MM/dd HH:mm:ss:SSS zzz</code>.</li> + * </ul> + * + * <p>In addition to looking for system properties with the names specified + * above, this implementation also checks for a class loader resource named + * <code>"simplelog.properties"</code>, and includes any matching definitions + * from this resource (if it exists).</p> + * + * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * + * @version $Id: SimpleLog.java 399221 2006-05-03 09:20:24Z dennisl $ + */ +public class SimpleLog implements Log, Serializable { + + + // ------------------------------------------------------- Class Attributes + + /** All system properties used by <code>SimpleLog</code> start with this */ + static protected final String systemPrefix = + "org.apache.commons.logging.simplelog."; + + /** Properties loaded from simplelog.properties */ + static protected final Properties simpleLogProps = new Properties(); + + /** The default format to use when formating dates */ + static protected final String DEFAULT_DATE_TIME_FORMAT = + "yyyy/MM/dd HH:mm:ss:SSS zzz"; + + /** Include the instance name in the log message? */ + static protected boolean showLogName = false; + /** Include the short name ( last component ) of the logger in the log + * message. Defaults to true - otherwise we'll be lost in a flood of + * messages without knowing who sends them. + */ + static protected boolean showShortName = true; + /** Include the current time in the log message */ + static protected boolean showDateTime = false; + /** The date and time format to use in the log message */ + static protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + /** Used to format times */ + static protected DateFormat dateFormatter = null; + + // ---------------------------------------------------- Log Level Constants + + + /** "Trace" level logging. */ + public static final int LOG_LEVEL_TRACE = 1; + /** "Debug" level logging. */ + public static final int LOG_LEVEL_DEBUG = 2; + /** "Info" level logging. */ + public static final int LOG_LEVEL_INFO = 3; + /** "Warn" level logging. */ + public static final int LOG_LEVEL_WARN = 4; + /** "Error" level logging. */ + public static final int LOG_LEVEL_ERROR = 5; + /** "Fatal" level logging. */ + public static final int LOG_LEVEL_FATAL = 6; + + /** Enable all logging levels */ + public static final int LOG_LEVEL_ALL = (LOG_LEVEL_TRACE - 1); + + /** Enable no logging levels */ + public static final int LOG_LEVEL_OFF = (LOG_LEVEL_FATAL + 1); + + // ------------------------------------------------------------ Initializer + + private static String getStringProperty(String name) { + String prop = null; + try { + prop = System.getProperty(name); + } catch (SecurityException e) { + ; // Ignore + } + return (prop == null) ? simpleLogProps.getProperty(name) : prop; + } + + private static String getStringProperty(String name, String dephault) { + String prop = getStringProperty(name); + return (prop == null) ? dephault : prop; + } + + private static boolean getBooleanProperty(String name, boolean dephault) { + String prop = getStringProperty(name); + return (prop == null) ? dephault : "true".equalsIgnoreCase(prop); + } + + // Initialize class attributes. + // Load properties file, if found. + // Override with system properties. + static { + // Add props from the resource simplelog.properties + InputStream in = getResourceAsStream("simplelog.properties"); + if(null != in) { + try { + simpleLogProps.load(in); + in.close(); + } catch(java.io.IOException e) { + // ignored + } + } + + showLogName = getBooleanProperty( systemPrefix + "showlogname", showLogName); + showShortName = getBooleanProperty( systemPrefix + "showShortLogname", showShortName); + showDateTime = getBooleanProperty( systemPrefix + "showdatetime", showDateTime); + + if(showDateTime) { + dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", + dateTimeFormat); + try { + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } catch(IllegalArgumentException e) { + // If the format pattern is invalid - use the default format + dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } + } + } + + + // ------------------------------------------------------------- Attributes + + /** The name of this simple log instance */ + protected String logName = null; + /** The current log level */ + protected int currentLogLevel; + /** The short name of this simple log instance */ + private String shortLogName = null; + + + // ------------------------------------------------------------ Constructor + + /** + * Construct a simple log with given name. + * + * @param name log name + */ + public SimpleLog(String name) { + + logName = name; + + // Set initial log level + // Used to be: set default log level to ERROR + // IMHO it should be lower, but at least info ( costin ). + setLevel(SimpleLog.LOG_LEVEL_INFO); + + // Set log level from properties + String lvl = getStringProperty(systemPrefix + "log." + logName); + int i = String.valueOf(name).lastIndexOf("."); + while(null == lvl && i > -1) { + name = name.substring(0,i); + lvl = getStringProperty(systemPrefix + "log." + name); + i = String.valueOf(name).lastIndexOf("."); + } + + if(null == lvl) { + lvl = getStringProperty(systemPrefix + "defaultlog"); + } + + if("all".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ALL); + } else if("trace".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_TRACE); + } else if("debug".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_DEBUG); + } else if("info".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_INFO); + } else if("warn".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_WARN); + } else if("error".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ERROR); + } else if("fatal".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_FATAL); + } else if("off".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_OFF); + } + + } + + + // -------------------------------------------------------- Properties + + /** + * <p> Set logging level. </p> + * + * @param currentLogLevel new logging level + */ + public void setLevel(int currentLogLevel) { + + this.currentLogLevel = currentLogLevel; + + } + + + /** + * <p> Get logging level. </p> + */ + public int getLevel() { + + return currentLogLevel; + } + + + // -------------------------------------------------------- Logging Methods + + + /** + * <p> Do the actual logging. + * This method assembles the message + * and then calls <code>write()</code> to cause it to be written.</p> + * + * @param type One of the LOG_LEVEL_XXX constants defining the log level + * @param message The message itself (typically a String) + * @param t The exception whose stack trace should be logged + */ + protected void log(int type, Object message, Throwable t) { + // Use a string buffer for better performance + StringBuffer buf = new StringBuffer(); + + // Append date-time if so configured + if(showDateTime) { + buf.append(dateFormatter.format(new Date())); + buf.append(" "); + } + + // Append a readable representation of the log level + switch(type) { + case SimpleLog.LOG_LEVEL_TRACE: buf.append("[TRACE] "); break; + case SimpleLog.LOG_LEVEL_DEBUG: buf.append("[DEBUG] "); break; + case SimpleLog.LOG_LEVEL_INFO: buf.append("[INFO] "); break; + case SimpleLog.LOG_LEVEL_WARN: buf.append("[WARN] "); break; + case SimpleLog.LOG_LEVEL_ERROR: buf.append("[ERROR] "); break; + case SimpleLog.LOG_LEVEL_FATAL: buf.append("[FATAL] "); break; + } + + // Append the name of the log instance if so configured + if( showShortName) { + if( shortLogName==null ) { + // Cut all but the last component of the name for both styles + shortLogName = logName.substring(logName.lastIndexOf(".") + 1); + shortLogName = + shortLogName.substring(shortLogName.lastIndexOf("/") + 1); + } + buf.append(String.valueOf(shortLogName)).append(" - "); + } else if(showLogName) { + buf.append(String.valueOf(logName)).append(" - "); + } + + // Append the message + buf.append(String.valueOf(message)); + + // Append stack trace if not null + if(t != null) { + buf.append(" <"); + buf.append(t.toString()); + buf.append(">"); + + java.io.StringWriter sw= new java.io.StringWriter(1024); + java.io.PrintWriter pw= new java.io.PrintWriter(sw); + t.printStackTrace(pw); + pw.close(); + buf.append(sw.toString()); + } + + // Print to the appropriate destination + write(buf); + + } + + + /** + * <p>Write the content of the message accumulated in the specified + * <code>StringBuffer</code> to the appropriate output destination. The + * default implementation writes to <code>System.err</code>.</p> + * + * @param buffer A <code>StringBuffer</code> containing the accumulated + * text to be logged + */ + protected void write(StringBuffer buffer) { + + System.err.println(buffer.toString()); + + } + + + /** + * Is the given log level currently enabled? + * + * @param logLevel is this level enabled? + */ + protected boolean isLevelEnabled(int logLevel) { + // log level are numerically ordered so can use simple numeric + // comparison + return (logLevel >= currentLogLevel); + } + + + // -------------------------------------------------------- Log Implementation + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public final void debug(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public final void debug(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public final void trace(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public final void trace(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public final void info(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO,message,null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public final void info(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public final void warn(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public final void warn(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public final void error(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public final void error(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, t); + } + } + + + /** + * Log a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public final void fatal(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public final void fatal(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, t); + } + } + + + /** + * <p> Are debug messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isDebugEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG); + } + + + /** + * <p> Are error messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isErrorEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR); + } + + + /** + * <p> Are fatal messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isFatalEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL); + } + + + /** + * <p> Are info messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isInfoEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO); + } + + + /** + * <p> Are trace messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isTraceEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE); + } + + + /** + * <p> Are warn messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isWarnEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN); + } + + + /** + * Return the thread context class loader if available. + * Otherwise return null. + * + * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + * + * @exception LogConfigurationException if a suitable class loader + * cannot be identified. + */ + private static ClassLoader getContextClassLoader() + { + ClassLoader classLoader = null; + + if (classLoader == null) { + try { + // Are we running on a JDK 1.2 or later system? + Method method = Thread.class.getMethod("getContextClassLoader", + (Class[]) null); + + // Get the thread context class loader (if there is one) + try { + classLoader = (ClassLoader)method.invoke(Thread.currentThread(), + (Object[]) null); + + } catch (IllegalAccessException e) { + ; // ignore + } catch (InvocationTargetException e) { + /** + * InvocationTargetException is thrown by 'invoke' when + * the method being invoked (getContextClassLoader) throws + * an exception. + * + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * In the first case (not related), we want to ignore and + * keep going. We cannot help but also ignore the second + * with the logic below, but other calls elsewhere (to + * obtain a class loader) will trigger this exception where + * we can make a distinction. + */ + if (e.getTargetException() instanceof SecurityException) { + ; // ignore + } else { + // Capture 'e.getTargetException()' exception for details + // alternate: log 'e.getTargetException()', and pass back 'e'. + throw new LogConfigurationException + ("Unexpected InvocationTargetException", e.getTargetException()); + } + } + } catch (NoSuchMethodException e) { + // Assume we are running on JDK 1.1 + ; // ignore + } + } + + if (classLoader == null) { + classLoader = SimpleLog.class.getClassLoader(); + } + + // Return the selected class loader + return classLoader; + } + + private static InputStream getResourceAsStream(final String name) + { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + ClassLoader threadCL = getContextClassLoader(); + + if (threadCL != null) { + return threadCL.getResourceAsStream(name); + } else { + return ClassLoader.getSystemResourceAsStream(name); + } + } + }); + } +} + diff --git a/src/org/apache/commons/logging/impl/WeakHashtable.java b/src/org/apache/commons/logging/impl/WeakHashtable.java new file mode 100644 index 0000000..e4749b6 --- /dev/null +++ b/src/org/apache/commons/logging/impl/WeakHashtable.java @@ -0,0 +1,478 @@ +/* + * Copyright 2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.*; + +/** + * <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s + * to hold its keys thus allowing them to be reclaimed by the garbage collector. + * The associated values are retained using strong references.</p> + * + * <p>This class follows the symantics of <code>Hashtable</code> as closely as + * possible. It therefore does not accept null values or keys.</p> + * + * <p><strong>Note:</strong> + * This is <em>not</em> intended to be a general purpose hash table replacement. + * This implementation is also tuned towards a particular purpose: for use as a replacement + * for <code>Hashtable</code> in <code>LogFactory</code>. This application requires + * good liveliness for <code>get</code> and <code>put</code>. Various tradeoffs + * have been made with this in mind. + * </p> + * <p> + * <strong>Usage:</strong> typical use case is as a drop-in replacement + * for the <code>Hashtable</code> used in <code>LogFactory</code> for J2EE enviroments + * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will + * allow classloaders to be collected by the garbage collector without the need + * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}. + * </p> + * + * <p><code>org.apache.commons.logging.LogFactory</code> checks whether this class + * can be supported by the current JVM, and if so then uses it to store + * references to the <code>LogFactory</code> implementationd it loads + * (rather than using a standard Hashtable instance). + * Having this class used instead of <code>Hashtable</code> solves + * certain issues related to dynamic reloading of applications in J2EE-style + * environments. However this class requires java 1.3 or later (due to its use + * of <code>java.lang.ref.WeakReference</code> and associates). + * And by the way, this extends <code>Hashtable</code> rather than <code>HashMap</code> + * for backwards compatibility reasons. See the documentation + * for method <code>LogFactory.createFactoryStore</code> for more details.</p> + * + * <p>The reason all this is necessary is due to a issue which + * arises during hot deploy in a J2EE-like containers. + * Each component running in the container owns one or more classloaders; when + * the component loads a LogFactory instance via the component classloader + * a reference to it gets stored in the static LogFactory.factories member, + * keyed by the component's classloader so different components don't + * stomp on each other. When the component is later unloaded, the container + * sets the component's classloader to null with the intent that all the + * component's classes get garbage-collected. However there's still a + * reference to the component's classloader from a key in the "global" + * <code>LogFactory</code>'s factories member! If <code>LogFactory.release()</code> + * is called whenever component is unloaded, the classloaders will be correctly + * garbage collected; this <i>should</i> be done by any container that + * bundles commons-logging by default. However, holding the classloader + * references weakly ensures that the classloader will be garbage collected + * without the container performing this step. </p> + * + * <p> + * <strong>Limitations:</strong> + * There is still one (unusual) scenario in which a component will not + * be correctly unloaded without an explicit release. Though weak references + * are used for its keys, it is necessary to use strong references for its values. + * </p> + * + * <p> If the abstract class <code>LogFactory</code> is + * loaded by the container classloader but a subclass of + * <code>LogFactory</code> [LogFactory1] is loaded by the component's + * classloader and an instance stored in the static map associated with the + * base LogFactory class, then there is a strong reference from the LogFactory + * class to the LogFactory1 instance (as normal) and a strong reference from + * the LogFactory1 instance to the component classloader via + * <code>getClass().getClassLoader()</code>. This chain of references will prevent + * collection of the child classloader.</p> + * + * <p> + * Such a situation occurs when the commons-logging.jar is + * loaded by a parent classloader (e.g. a server level classloader in a + * servlet container) and a custom <code>LogFactory</code> implementation is + * loaded by a child classloader (e.g. a web app classloader).</p> + * + * <p>To avoid this scenario, ensure + * that any custom LogFactory subclass is loaded by the same classloader as + * the base <code>LogFactory</code>. Creating custom LogFactory subclasses is, + * however, rare. The standard LogFactoryImpl class should be sufficient + * for most or all users.</p> + * + * + * @author Brian Stansberry + * + * @since 1.1 + */ +public final class WeakHashtable extends Hashtable { + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of all cleared entries. + */ + private static final int MAX_CHANGES_BEFORE_PURGE = 100; + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of one cleared entry. + */ + private static final int PARTIAL_PURGE_COUNT = 10; + + /* ReferenceQueue we check for gc'd keys */ + private ReferenceQueue queue = new ReferenceQueue(); + /* Counter used to control how often we purge gc'd entries */ + private int changeCount = 0; + + /** + * Constructs a WeakHashtable with the Hashtable default + * capacity and load factor. + */ + public WeakHashtable() {} + + + /** + *@see Hashtable + */ + public boolean containsKey(Object key) { + // purge should not be required + Referenced referenced = new Referenced(key); + return super.containsKey(referenced); + } + + /** + *@see Hashtable + */ + public Enumeration elements() { + purge(); + return super.elements(); + } + + /** + *@see Hashtable + */ + public Set entrySet() { + purge(); + Set referencedEntries = super.entrySet(); + Set unreferencedEntries = new HashSet(); + for (Iterator it=referencedEntries.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Referenced referencedKey = (Referenced) entry.getKey(); + Object key = referencedKey.getValue(); + Object value = entry.getValue(); + if (key != null) { + Entry dereferencedEntry = new Entry(key, value); + unreferencedEntries.add(dereferencedEntry); + } + } + return unreferencedEntries; + } + + /** + *@see Hashtable + */ + public Object get(Object key) { + // for performance reasons, no purge + Referenced referenceKey = new Referenced(key); + return super.get(referenceKey); + } + + /** + *@see Hashtable + */ + public Enumeration keys() { + purge(); + final Enumeration enumer = super.keys(); + return new Enumeration() { + public boolean hasMoreElements() { + return enumer.hasMoreElements(); + } + public Object nextElement() { + Referenced nextReference = (Referenced) enumer.nextElement(); + return nextReference.getValue(); + } + }; + } + + + /** + *@see Hashtable + */ + public Set keySet() { + purge(); + Set referencedKeys = super.keySet(); + Set unreferencedKeys = new HashSet(); + for (Iterator it=referencedKeys.iterator(); it.hasNext();) { + Referenced referenceKey = (Referenced) it.next(); + Object keyValue = referenceKey.getValue(); + if (keyValue != null) { + unreferencedKeys.add(keyValue); + } + } + return unreferencedKeys; + } + + /** + *@see Hashtable + */ + public Object put(Object key, Object value) { + // check for nulls, ensuring symantics match superclass + if (key == null) { + throw new NullPointerException("Null keys are not allowed"); + } + if (value == null) { + throw new NullPointerException("Null values are not allowed"); + } + + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) { + purgeOne(); + } + + Object result = null; + Referenced keyRef = new Referenced(key, queue); + return super.put(keyRef, value); + } + + /** + *@see Hashtable + */ + public void putAll(Map t) { + if (t != null) { + Set entrySet = t.entrySet(); + for (Iterator it=entrySet.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + *@see Hashtable + */ + public Collection values() { + purge(); + return super.values(); + } + + /** + *@see Hashtable + */ + public Object remove(Object key) { + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) { + purgeOne(); + } + return super.remove(new Referenced(key)); + } + + /** + *@see Hashtable + */ + public boolean isEmpty() { + purge(); + return super.isEmpty(); + } + + /** + *@see Hashtable + */ + public int size() { + purge(); + return super.size(); + } + + /** + *@see Hashtable + */ + public String toString() { + purge(); + return super.toString(); + } + + /** + * @see Hashtable + */ + protected void rehash() { + // purge here to save the effort of rehashing dead entries + purge(); + super.rehash(); + } + + /** + * Purges all entries whose wrapped keys + * have been garbage collected. + */ + private void purge() { + synchronized (queue) { + WeakKey key; + while ((key = (WeakKey) queue.poll()) != null) { + super.remove(key.getReferenced()); + } + } + } + + /** + * Purges one entry whose wrapped key + * has been garbage collected. + */ + private void purgeOne() { + + synchronized (queue) { + WeakKey key = (WeakKey) queue.poll(); + if (key != null) { + super.remove(key.getReferenced()); + } + } + } + + /** Entry implementation */ + private final static class Entry implements Map.Entry { + + private final Object key; + private final Object value; + + private Entry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + boolean result = false; + if (o != null && o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + result = (getKey()==null ? + entry.getKey() == null : + getKey().equals(entry.getKey())) + && + (getValue()==null ? + entry.getValue() == null : + getValue().equals(entry.getValue())); + } + return result; + } + + public int hashCode() { + + return (getKey()==null ? 0 : getKey().hashCode()) ^ + (getValue()==null ? 0 : getValue().hashCode()); + } + + public Object setValue(Object value) { + throw new UnsupportedOperationException("Entry.setValue is not supported."); + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + } + + + /** Wrapper giving correct symantics for equals and hashcode */ + private final static class Referenced { + + private final WeakReference reference; + private final int hashCode; + + /** + * + * @throws NullPointerException if referant is <code>null</code> + */ + private Referenced(Object referant) { + reference = new WeakReference(referant); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = referant.hashCode(); + } + + /** + * + * @throws NullPointerException if key is <code>null</code> + */ + private Referenced(Object key, ReferenceQueue queue) { + reference = new WeakKey(key, queue, this); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = key.hashCode(); + + } + + public int hashCode() { + return hashCode; + } + + private Object getValue() { + return reference.get(); + } + + public boolean equals(Object o) { + boolean result = false; + if (o instanceof Referenced) { + Referenced otherKey = (Referenced) o; + Object thisKeyValue = getValue(); + Object otherKeyValue = otherKey.getValue(); + if (thisKeyValue == null) { + result = (otherKeyValue == null); + + // Since our hashcode was calculated from the original + // non-null referant, the above check breaks the + // hashcode/equals contract, as two cleared Referenced + // objects could test equal but have different hashcodes. + // We can reduce (not eliminate) the chance of this + // happening by comparing hashcodes. + if (result == true) { + result = (this.hashCode() == otherKey.hashCode()); + } + // In any case, as our c'tor does not allow null referants + // and Hashtable does not do equality checks between + // existing keys, normal hashtable operations should never + // result in an equals comparison between null referants + } + else + { + result = thisKeyValue.equals(otherKeyValue); + } + } + return result; + } + } + + /** + * WeakReference subclass that holds a hard reference to an + * associated <code>value</code> and also makes accessible + * the Referenced object holding it. + */ + private final static class WeakKey extends WeakReference { + + private final Referenced referenced; + + private WeakKey(Object key, + ReferenceQueue queue, + Referenced referenced) { + super(key, queue); + this.referenced = referenced; + } + + private Referenced getReferenced() { + return referenced; + } + } +} diff --git a/src/org/apache/commons/logging/impl/package.html b/src/org/apache/commons/logging/impl/package.html new file mode 100644 index 0000000..eb26b76 --- /dev/null +++ b/src/org/apache/commons/logging/impl/package.html @@ -0,0 +1,21 @@ +<!-- + + Copyright 2001-2004 The Apache Software Foundation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + +<body> +<p>Concrete implementations of commons-logging wrapper APIs.</p> +</body> diff --git a/src/org/apache/commons/logging/package.html b/src/org/apache/commons/logging/package.html new file mode 100644 index 0000000..cfde4f0 --- /dev/null +++ b/src/org/apache/commons/logging/package.html @@ -0,0 +1,254 @@ +<!-- + + Copyright 2001-2004 The Apache Software Foundation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + +<body> +<p>Simple wrapper API around multiple logging APIs.</p> + + +<h3>Overview</h3> + +<p>This package provides an API for logging in server-based applications that +can be used around a variety of different logging implementations, including +prebuilt support for the following:</p> +<ul> +<li><a href="http://logging.apache.org/log4j/">Log4J</a> (version 1.2 or later) + from Apache's Jakarta project. Each named <a href="Log.html">Log</a> + instance is connected to a corresponding Log4J Logger.</li> +<li><a href="http://java.sun.com/j2se/1.4/docs/guide/util/logging/index.html"> + JDK Logging API</a>, included in JDK 1.4 or later systems. Each named + <a href="Log.html">Log</a> instance is connected to a corresponding + <code>java.util.logging.Logger</code> instance.</li> +<li><a href="http://avalon.apache.org/logkit/">LogKit</a> from Apache's + Avalon project. Each named <a href="Log.html">Log</a> instance is + connected to a corresponding LogKit <code>Logger</code>.</li> +<li><a href="impl/NoOpLog.html">NoOpLog</a> implementation that simply swallows + all log output, for all named <a href="Log.html">Log</a> instances.</li> +<li><a href="impl/SimpleLog.html">SimpleLog</a> implementation that writes all + log output, for all named <a href="Log.html">Log</a> instances, to + System.err.</li> +</ul> + + +<h3>Quick Start Guide</h3> + +<p>For those impatient to just get on with it, the following example +illustrates the typical declaration and use of a logger that is named (by +convention) after the calling class: + +<pre> + import org.apache.commons.logging.Log; + import org.apache.commons.logging.LogFactory; + + public class Foo { + + private Log log = LogFactory.getLog(Foo.class); + + public void foo() { + ... + try { + if (log.isDebugEnabled()) { + log.debug("About to do something to object " + name); + } + name.bar(); + } catch (IllegalStateException e) { + log.error("Something bad happened to " + name, e); + } + ... + } +</pre> + +<p>Unless you configure things differently, all log output will be written +to System.err. Therefore, you really will want to review the remainder of +this page in order to understand how to configure logging for your +application.</p> + + +<h3>Configuring the Commons Logging Package</h3> + + +<h4>Choosing a <code>LogFactory</code> Implementation</h4> + +<p>From an application perspective, the first requirement is to retrieve an +object reference to the <code>LogFactory</code> instance that will be used +to create <code><a href="Log.html">Log</a></code> instances for this +application. This is normally accomplished by calling the static +<code>getFactory()</code> method. This method implements the following +discovery algorithm to select the name of the <code>LogFactory</code> +implementation class this application wants to use:</p> +<ul> +<li>Check for a system property named + <code>org.apache.commons.logging.LogFactory</code>.</li> +<li>Use the JDK 1.3 JAR Services Discovery mechanism (see + <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html"> + http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html</a> for + more information) to look for a resource named + <code>META-INF/services/org.apache.commons.logging.LogFactory</code> + whose first line is assumed to contain the desired class name.</li> +<li>Look for a properties file named <code>commons-logging.properties</code> + visible in the application class path, with a property named + <code>org.apache.commons.logging.LogFactory</code> defining the + desired implementation class name.</li> +<li>Fall back to a default implementation, which is described + further below.</li> +</ul> + +<p>If a <code>commons-logging.properties</code> file is found, all of the +properties defined there are also used to set configuration attributes on +the instantiated <code>LogFactory</code> instance.</p> + +<p>Once an implementation class name is selected, the corresponding class is +loaded from the current Thread context class loader (if there is one), or +from the class loader that loaded the <code>LogFactory</code> class itself +otherwise. This allows a copy of <code>commons-logging.jar</code> to be +shared in a multiple class loader environment (such as a servlet container), +but still allow each web application to provide its own <code>LogFactory</code> +implementation, if it so desires. An instance of this class will then be +created, and cached per class loader. + + +<h4>The Default <code>LogFactory</code> Implementation</h4> + +<p>The Logging Package APIs include a default <code>LogFactory</code> +implementation class (<a href="impl/LogFactoryImpl.html"> +org.apache.commons.logging.impl.LogFactoryImpl</a>) that is selected if no +other implementation class name can be discovered. Its primary purpose is +to create (as necessary) and return <a href="Log.html">Log</a> instances +in response to calls to the <code>getInstance()</code> method. The default +implementation uses the following rules:</p> +<ul> +<li>At most one <code>Log</code> instance of the same name will be created. + Subsequent <code>getInstance()</code> calls to the same + <code>LogFactory</code> instance, with the same name or <code>Class</code> + parameter, will return the same <code>Log</code> instance.</li> +<li>When a new <code>Log</code> instance must be created, the default + <code>LogFactory</code> implementation uses the following discovery + process: + <ul> + <li>Look for a configuration attribute of this factory named + <code>org.apache.commons.logging.Log</code> (for backwards + compatibility to pre-1.0 versions of this API, an attribute + <code>org.apache.commons.logging.log</code> is also consulted).</li> + <li>Look for a system property named + <code>org.apache.commons.logging.Log</code> (for backwards + compatibility to pre-1.0 versions of this API, a system property + <code>org.apache.commons.logging.log</code> is also consulted).</li> + <li>If the Log4J logging system is available in the application + class path, use the corresponding wrapper class + (<a href="impl/Log4JLogger.html">Log4JLogger</a>).</li> + <li>If the application is executing on a JDK 1.4 system, use + the corresponding wrapper class + (<a href="impl/Jdk14Logger.html">Jdk14Logger</a>).</li> + <li>Fall back to the default simple logging implementation + (<a href="impl/SimpleLog.html">SimpleLog</a>).</li> + </ul></li> +<li>Load the class of the specified name from the thread context class + loader (if any), or from the class loader that loaded the + <code>LogFactory</code> class otherwise.</li> +<li>Instantiate an instance of the selected <code>Log</code> + implementation class, passing the specified name as the single + argument to its constructor.</li> +</ul> + +<p>See the <a href="impl/SimpleLog.html">SimpleLog</a> JavaDocs for detailed +configuration information for this default implementation.</p> + + +<h4>Configuring the Underlying Logging System</h4> + +<p>The basic principle is that the user is totally responsible for the +configuration of the underlying logging system. +Commons-logging should not change the existing configuration.</p> + +<p>Each individual <a href="Log.html">Log</a> implementation may +support its own configuration properties. These will be documented in the +class descriptions for the corresponding implementation class.</p> + +<p>Finally, some <code>Log</code> implementations (such as the one for Log4J) +require an external configuration file for the entire logging environment. +This file should be prepared in a manner that is specific to the actual logging +technology being used.</p> + + +<h3>Using the Logging Package APIs</h3> + +<p>Use of the Logging Package APIs, from the perspective of an application +component, consists of the following steps:</p> +<ol> +<li>Acquire a reference to an instance of + <a href="Log.html">org.apache.commons.logging.Log</a>, by calling the + factory method + <a href="LogFactory.html#getInstance(java.lang.String)"> + LogFactory.getInstance(String name)</a>. Your application can contain + references to multiple loggers that are used for different + purposes. A typical scenario for a server application is to have each + major component of the server use its own Log instance.</li> +<li>Cause messages to be logged (if the corresponding detail level is enabled) + by calling appropriate methods (<code>trace()</code>, <code>debug()</code>, + <code>info()</code>, <code>warn()</code>, <code>error</code>, and + <code>fatal()</code>).</li> +</ol> + +<p>For convenience, <code>LogFactory</code> also offers a static method +<code>getLog()</code> that combines the typical two-step pattern:</p> +<pre> + Log log = LogFactory.getFactory().getInstance(Foo.class); +</pre> +<p>into a single method call:</p> +<pre> + Log log = LogFactory.getLog(Foo.class); +</pre> + +<p>For example, you might use the following technique to initialize and +use a <a href="Log.html">Log</a> instance in an application component:</p> +<pre> +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class MyComponent { + + protected Log log = + LogFactory.getLog(MyComponent.class); + + // Called once at startup time + public void start() { + ... + log.info("MyComponent started"); + ... + } + + // Called once at shutdown time + public void stop() { + ... + log.info("MyComponent stopped"); + ... + } + + // Called repeatedly to process a particular argument value + // which you want logged if debugging is enabled + public void process(String value) { + ... + // Do the string concatenation only if logging is enabled + if (log.isDebugEnabled()) + log.debug("MyComponent processing " + value); + ... + } + +} +</pre> + +</body> diff --git a/src/org/apache/http/ConnectionClosedException.java b/src/org/apache/http/ConnectionClosedException.java new file mode 100644 index 0000000..fa0e2db --- /dev/null +++ b/src/org/apache/http/ConnectionClosedException.java @@ -0,0 +1,58 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/ConnectionClosedException.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +/** + * Indicates that a connection has been closed. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public class ConnectionClosedException extends IOException { + + private static final long serialVersionUID = 617550366255636674L; + + /** + * Creates a new ConnectionClosedException with the specified detail message. + * + * @param message The exception detail message + */ + public ConnectionClosedException(final String message) { + super(message); + } + +} diff --git a/src/org/apache/http/ConnectionReuseStrategy.java b/src/org/apache/http/ConnectionReuseStrategy.java new file mode 100644 index 0000000..635cc5c --- /dev/null +++ b/src/org/apache/http/ConnectionReuseStrategy.java @@ -0,0 +1,75 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/ConnectionReuseStrategy.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import org.apache.http.protocol.HttpContext; + +/** + * Interface for deciding whether a connection should be kept alive. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public interface ConnectionReuseStrategy { + + /** + * Decides whether a connection can be kept open after a request. + * If this method returns <code>false</code>, the caller MUST + * close the connection to correctly implement the HTTP protocol. + * If it returns <code>true</code>, the caller SHOULD attempt to + * keep the connection open for reuse with another request. + * <br/> + * One can use the HTTP context to retrieve additional objects that + * may be relevant for the keep-alive strategy: the actual HTTP + * connection, the original HTTP request, target host if known, + * number of times the connection has been reused already and so on. + * <br/> + * If the connection is already closed, <code>false</code> is returned. + * The stale connection check MUST NOT be triggered by a + * connection reuse strategy. + * + * @param response + * The last response received over that connection. + * @param context the context in which the connection is being + * used. + * + * @return <code>true</code> if the connection is allowed to be reused, or + * <code>false</code> if it MUST NOT be reused + */ + boolean keepAlive(HttpResponse response, HttpContext context); + +} diff --git a/src/org/apache/http/FormattedHeader.java b/src/org/apache/http/FormattedHeader.java new file mode 100644 index 0000000..04ea279 --- /dev/null +++ b/src/org/apache/http/FormattedHeader.java @@ -0,0 +1,68 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/FormattedHeader.java $ + * $Revision: 569781 $ + * $Date: 2007-08-26 02:05:06 -0700 (Sun, 26 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import org.apache.http.util.CharArrayBuffer; + +/** + * An HTTP header which is already formatted. + * For example when headers are received, the original formatting + * can be preserved. This allows for the header to be sent without + * another formatting step. + * + * + * @version $Revision: 569781 $ + */ +public interface FormattedHeader extends Header { + + + /** + * Obtains the buffer with the formatted header. + * The returned buffer MUST NOT be modified. + * + * @return the formatted header, in a buffer that must not be modified + */ + CharArrayBuffer getBuffer() + ; + + /** + * Obtains the start of the header value in the {@link #getBuffer buffer}. + * By accessing the value in the buffer, creation of a temporary string + * can be avoided. + * + * @return index of the first character of the header value + * in the buffer returned by {@link #getBuffer getBuffer}. + */ + int getValuePos() + ; + +} diff --git a/src/org/apache/http/Header.java b/src/org/apache/http/Header.java new file mode 100644 index 0000000..4e04bec --- /dev/null +++ b/src/org/apache/http/Header.java @@ -0,0 +1,64 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/Header.java $ + * $Revision: 569636 $ + * $Date: 2007-08-25 00:34:47 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * Represents an HTTP header field. + * + * <p>The HTTP header fields follow the same generic format as + * that given in Section 3.1 of RFC 822. Each header field consists + * of a name followed by a colon (":") and the field value. Field names + * are case-insensitive. The field value MAY be preceded by any amount + * of LWS, though a single SP is preferred. + * + *<pre> + * message-header = field-name ":" [ field-value ] + * field-name = token + * field-value = *( field-content | LWS ) + * field-content = <the OCTETs making up the field-value + * and consisting of either *TEXT or combinations + * of token, separators, and quoted-string> + *</pre> + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @version $Revision: 569636 $ + */ +public interface Header { + + String getName(); + + String getValue(); + + HeaderElement[] getElements() throws ParseException; + +} diff --git a/src/org/apache/http/HeaderElement.java b/src/org/apache/http/HeaderElement.java new file mode 100644 index 0000000..ddc4a9e --- /dev/null +++ b/src/org/apache/http/HeaderElement.java @@ -0,0 +1,59 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HeaderElement.java $ + * $Revision: 569828 $ + * $Date: 2007-08-26 08:49:38 -0700 (Sun, 26 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * One element of an HTTP {@link Header header} value. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 569828 $ $Date: 2007-08-26 08:49:38 -0700 (Sun, 26 Aug 2007) $ + * + * @since 4.0 + */ +public interface HeaderElement { + + String getName(); + + String getValue(); + + NameValuePair[] getParameters(); + + NameValuePair getParameterByName(String name); + + int getParameterCount(); + + NameValuePair getParameter(int index); +} + diff --git a/src/org/apache/http/HeaderElementIterator.java b/src/org/apache/http/HeaderElementIterator.java new file mode 100644 index 0000000..14137f0 --- /dev/null +++ b/src/org/apache/http/HeaderElementIterator.java @@ -0,0 +1,61 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HeaderElementIterator.java $ + * $Revision: 584542 $ + * $Date: 2007-10-14 06:29:34 -0700 (Sun, 14 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.util.Iterator; + +/** + * A type-safe iterator for {@link HeaderElement HeaderElement} objects. + * + * @version $Revision: 584542 $ + */ +public interface HeaderElementIterator extends Iterator { + + /** + * Indicates whether there is another header element in this + * iteration. + * + * @return <code>true</code> if there is another header element, + * <code>false</code> otherwise + */ + boolean hasNext(); + + /** + * Obtains the next header element from this iteration. + * This method should only be called while {@link #hasNext hasNext} + * is true. + * + * @return the next header element in this iteration + */ + HeaderElement nextElement(); + +} diff --git a/src/org/apache/http/HeaderIterator.java b/src/org/apache/http/HeaderIterator.java new file mode 100644 index 0000000..688b611 --- /dev/null +++ b/src/org/apache/http/HeaderIterator.java @@ -0,0 +1,64 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HeaderIterator.java $ + * $Revision: 581981 $ + * $Date: 2007-10-04 11:26:26 -0700 (Thu, 04 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + + +import java.util.Iterator; + + +/** + * A type-safe iterator for {@link Header Header} objects. + * + * @version $Revision: 581981 $ + */ +public interface HeaderIterator extends Iterator { + + /** + * Indicates whether there is another header in this iteration. + * + * @return <code>true</code> if there is another header, + * <code>false</code> otherwise + */ + boolean hasNext() + ; + + + /** + * Obtains the next header from this iteration. + * This method should only be called while {@link #hasNext hasNext} + * is true. + * + * @return the next header in this iteration + */ + Header nextHeader() + ; +} diff --git a/src/org/apache/http/HttpClientConnection.java b/src/org/apache/http/HttpClientConnection.java new file mode 100644 index 0000000..a38c8f3 --- /dev/null +++ b/src/org/apache/http/HttpClientConnection.java @@ -0,0 +1,112 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpClientConnection.java $ + * $Revision: 542199 $ + * $Date: 2007-05-28 04:23:46 -0700 (Mon, 28 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +/** + * An HTTP connection for use on the client side. + * It is used for sending requests and receiving responses. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 542199 $ + * + * @since 4.0 + */ +public interface HttpClientConnection extends HttpConnection { + + /** + * Checks if response data is available from the connection. May wait for + * the specified time until some data becomes available. Note that some + * implementations may completely ignore the timeout parameter. + * + * @param timeout the maximum time in milliseconds to wait for data + * @return true if data is available; false if there was no data available + * even after waiting for <code>timeout</code> milliseconds. + * @throws IOException if an error happens on the connection + */ + boolean isResponseAvailable(int timeout) + throws IOException; + + /** + * Sends the request line and all headers over the connection. + * @param request the request whose headers to send. + * @throws HttpException + * @throws IOException + */ + void sendRequestHeader(HttpRequest request) + throws HttpException, IOException; + + /** + * Sends the request entity over the connection. + * @param request the request whose entity to send. + * @throws HttpException + * @throws IOException + */ + void sendRequestEntity(HttpEntityEnclosingRequest request) + throws HttpException, IOException; + + /** + * Receives the request line and headers of the next response available from + * this connection. The caller should examine the HttpResponse object to + * find out if it should try to receive a response entity as well. + * + * @return a new HttpResponse object with status line and headers + * initialized. + * @throws HttpException + * @throws IOException + */ + HttpResponse receiveResponseHeader() + throws HttpException, IOException; + + /** + * Receives the next response entity available from this connection and + * attaches it to an existing HttpResponse object. + * + * @param response the response to attach the entity to + * @throws HttpException + * @throws IOException + */ + void receiveResponseEntity(HttpResponse response) + throws HttpException, IOException; + + /** + * Writes out all pending buffered data over the open connection. + * + * @throws IOException + */ + void flush() throws IOException; + +} diff --git a/src/org/apache/http/HttpConnection.java b/src/org/apache/http/HttpConnection.java new file mode 100644 index 0000000..a3311f8 --- /dev/null +++ b/src/org/apache/http/HttpConnection.java @@ -0,0 +1,110 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpConnection.java $ + * $Revision: 548031 $ + * $Date: 2007-06-17 04:28:38 -0700 (Sun, 17 Jun 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +/** + * A generic HTTP connection, useful on client and server side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 548031 $ + * + * @since 4.0 + */ +public interface HttpConnection { + + /** + * Closes this connection gracefully. + * This method will attempt to flush the transmitter's + * internal buffer prior to closing the underlying socket. + * This method MUST NOT be called from a different thread to force + * shutdown of the connection. Use {@link #shutdown shutdown} instead. + */ + public void close() throws IOException; + + /** + * Checks if this connection is open. + * @return true if it is open, false if it is closed. + */ + public boolean isOpen(); + + /** + * Checks whether this connection has gone down. + * Network connections may get closed during some time of inactivity + * for several reasons. The next time a read is attempted on such a + * connection it will throw an IOException. + * This method tries to alleviate this inconvenience by trying to + * find out if a connection is still usable. Implementations may do + * that by attempting a read with a very small timeout. Thus this + * method may block for a small amount of time before returning a result. + * It is therefore an <i>expensive</i> operation. + * + * @return <code>true</code> if attempts to use this connection are + * likely to succeed, or <code>false</code> if they are likely + * to fail and this connection should be closed + */ + public boolean isStale(); + + /** + * Sets the socket timeout value. + * + * @param timeout timeout value in milliseconds + */ + void setSocketTimeout(int timeout); + + /** + * Returns the socket timeout value. + * + * @return positive value in milliseconds if a timeout is set, + * <code>0</code> if timeout is disabled or <code>-1</code> if + * timeout is undefined. + */ + int getSocketTimeout(); + + /** + * Force-closes this connection. + * This is the only method of a connection which may be called + * from a different thread to terminate the connection. + * This method will not attempt to flush the transmitter's + * internal buffer prior to closing the underlying socket. + */ + public void shutdown() throws IOException; + + /** + * Returns a collection of connection metrcis + * @return HttpConnectionMetrics + */ + HttpConnectionMetrics getMetrics(); + +} diff --git a/src/org/apache/http/HttpConnectionMetrics.java b/src/org/apache/http/HttpConnectionMetrics.java new file mode 100644 index 0000000..289dd46 --- /dev/null +++ b/src/org/apache/http/HttpConnectionMetrics.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpConnectionMetrics.java $ + * $Revision: 548035 $ + * $Date: 2007-06-17 05:17:03 -0700 (Sun, 17 Jun 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * The point of access to the statistics of an {@link HttpConnection}. + */ +public interface HttpConnectionMetrics { + + /** + * Returns the number of requests transferred over the connection, + * 0 if not available. + */ + long getRequestCount(); + + /** + * Returns the number of responses transferred over the connection, + * 0 if not available. + */ + long getResponseCount(); + + /** + * Returns the number of bytes transferred over the connection, + * 0 if not available. + */ + long getSentBytesCount(); + + /** + * Returns the number of bytes transferred over the connection, + * 0 if not available. + */ + long getReceivedBytesCount(); + + /** + * Return the value for the specified metric. + * + *@param metricName the name of the metric to query. + * + *@return the object representing the metric requested, + * <code>null</code> if the metric cannot not found. + */ + Object getMetric(String metricName); + + /** + * Resets the counts + * + */ + void reset(); + +} diff --git a/src/org/apache/http/HttpEntity.java b/src/org/apache/http/HttpEntity.java new file mode 100644 index 0000000..51ddafc --- /dev/null +++ b/src/org/apache/http/HttpEntity.java @@ -0,0 +1,196 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpEntity.java $ + * $Revision: 645824 $ + * $Date: 2008-04-08 03:12:41 -0700 (Tue, 08 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An entity that can be sent or received with an HTTP message. + * Entities can be found in some + * {@link HttpEntityEnclosingRequest requests} and in + * {@link HttpResponse responses}, where they are optional. + * <p> + * In some places, the JavaDoc distinguishes three kinds of entities, + * depending on where their {@link #getContent content} originates: + * <ul> + * <li><b>streamed</b>: The content is received from a stream, or + * generated on the fly. In particular, this category includes + * entities being received from a {@link HttpConnection connection}. + * {@link #isStreaming Streamed} entities are generally not + * {@link #isRepeatable repeatable}. + * </li> + * <li><b>self-contained</b>: The content is in memory or obtained by + * means that are independent from a connection or other entity. + * Self-contained entities are generally {@link #isRepeatable repeatable}. + * </li> + * <li><b>wrapping</b>: The content is obtained from another entity. + * </li> + * </ul> + * This distinction is important for connection management with incoming + * entities. For entities that are created by an application and only sent + * using the HTTP components framework, the difference between streamed + * and self-contained is of little importance. In that case, it is suggested + * to consider non-repeatable entities as streamed, and those that are + * repeatable (without a huge effort) as self-contained. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 645824 $ + * + * @since 4.0 + */ +public interface HttpEntity { + + /** + * Tells if the entity is capable to produce its data more than once. + * A repeatable entity's getContent() and writeTo(OutputStream) methods + * can be called more than once whereas a non-repeatable entity's can not. + * @return true if the entity is repeatable, false otherwise. + */ + boolean isRepeatable(); + + /** + * Tells about chunked encoding for this entity. + * The primary purpose of this method is to indicate whether + * chunked encoding should be used when the entity is sent. + * For entities that are received, it can also indicate whether + * the entity was received with chunked encoding. + * <br/> + * The behavior of wrapping entities is implementation dependent, + * but should respect the primary purpose. + * + * @return <code>true</code> if chunked encoding is preferred for this + * entity, or <code>false</code> if it is not + */ + boolean isChunked(); + + /** + * Tells the length of the content, if known. + * + * @return the number of bytes of the content, or + * a negative number if unknown. If the content length is known + * but exceeds {@link java.lang.Long#MAX_VALUE Long.MAX_VALUE}, + * a negative number is returned. + */ + long getContentLength(); + + /** + * Obtains the Content-Type header, if known. + * This is the header that should be used when sending the entity, + * or the one that was received with the entity. It can include a + * charset attribute. + * + * @return the Content-Type header for this entity, or + * <code>null</code> if the content type is unknown + */ + Header getContentType(); + + /** + * Obtains the Content-Encoding header, if known. + * This is the header that should be used when sending the entity, + * or the one that was received with the entity. + * Wrapping entities that modify the content encoding should + * adjust this header accordingly. + * + * @return the Content-Encoding header for this entity, or + * <code>null</code> if the content encoding is unknown + */ + Header getContentEncoding(); + + /** + * Creates a new InputStream object of the entity. + * It is a programming error + * to return the same InputStream object more than once. + * Entities that are not {@link #isRepeatable repeatable} + * will throw an exception if this method is called multiple times. + * + * @return a new input stream that returns the entity data. + * + * @throws IOException if the stream could not be created + * @throws IllegalStateException + * if this entity is not repeatable and the stream + * has already been obtained previously + */ + InputStream getContent() throws IOException, IllegalStateException; + + /** + * Writes the entity content to the output stream. + * + * @param outstream the output stream to write entity content to + * + * @throws IOException if an I/O error occurs + */ + void writeTo(OutputStream outstream) throws IOException; + + /** + * Tells whether this entity depends on an underlying stream. + * Streamed entities should return <code>true</code> until the + * content has been consumed, <code>false</code> afterwards. + * Self-contained entities should return <code>false</code>. + * Wrapping entities should delegate this call to the wrapped entity. + * <br/> + * The content of a streamed entity is consumed when the stream + * returned by {@link #getContent getContent} has been read to EOF, + * or after {@link #consumeContent consumeContent} has been called. + * If a streamed entity can not detect whether the stream has been + * read to EOF, it should return <code>true</code> until + * {@link #consumeContent consumeContent} is called. + * + * @return <code>true</code> if the entity content is streamed and + * not yet consumed, <code>false</code> otherwise + */ + boolean isStreaming(); // don't expect an exception here + + /** + * TODO: The name of this method is misnomer. It will be renamed to + * #finish() in the next major release. + * <br/> + * This method is called to indicate that the content of this entity + * is no longer required. All entity implementations are expected to + * release all allocated resources as a result of this method + * invocation. Content streaming entities are also expected to + * dispose of the remaining content, if any. Wrapping entities should + * delegate this call to the wrapped entity. + * <br/> + * This method is of particular importance for entities being + * received from a {@link HttpConnection connection}. The entity + * needs to be consumed completely in order to re-use the connection + * with keep-alive. + * + * @throws IOException if an I/O error occurs. + * This indicates that connection keep-alive is not possible. + */ + void consumeContent() throws IOException; + +} // interface HttpEntity diff --git a/src/org/apache/http/HttpEntityEnclosingRequest.java b/src/org/apache/http/HttpEntityEnclosingRequest.java new file mode 100644 index 0000000..c47c32b --- /dev/null +++ b/src/org/apache/http/HttpEntityEnclosingRequest.java @@ -0,0 +1,63 @@ +/* + * $Header: $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * A request with an entity. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public interface HttpEntityEnclosingRequest extends HttpRequest { + + /** + * Tells if this request should use the expect-continue handshake. + * The expect continue handshake gives the server a chance to decide + * whether to accept the entity enclosing request before the possibly + * lengthy entity is sent across the wire. + * @return true if the expect continue handshake should be used, false if + * not. + */ + boolean expectContinue(); + + /** + * Hands the entity to the request. + * @param entity the entity to send. + */ + void setEntity(HttpEntity entity); + + HttpEntity getEntity(); + +} diff --git a/src/org/apache/http/HttpException.java b/src/org/apache/http/HttpException.java new file mode 100644 index 0000000..77aacb1 --- /dev/null +++ b/src/org/apache/http/HttpException.java @@ -0,0 +1,75 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpException.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import org.apache.http.util.ExceptionUtils; + +/** + * Signals that an HTTP exception has occurred. + * + * @author Laura Werner + * + * @version $Revision: 618017 $ $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + */ +public class HttpException extends Exception { + + private static final long serialVersionUID = -5437299376222011036L; + + /** + * Creates a new HttpException with a <tt>null</tt> detail message. + */ + public HttpException() { + super(); + } + + /** + * Creates a new HttpException with the specified detail message. + * + * @param message the exception detail message + */ + public HttpException(final String message) { + super(message); + } + + /** + * Creates a new HttpException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public HttpException(final String message, final Throwable cause) { + super(message); + ExceptionUtils.initCause(this, cause); + } + +} diff --git a/src/org/apache/http/HttpHost.java b/src/org/apache/http/HttpHost.java new file mode 100644 index 0000000..869f5af --- /dev/null +++ b/src/org/apache/http/HttpHost.java @@ -0,0 +1,218 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpHost.java $ + * $Revision: 653058 $ + * $Date: 2008-05-03 05:01:10 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.util.Locale; + +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.LangUtils; + +/** + * Holds all of the variables needed to describe an HTTP connection to a host. + * This includes remote host name, port and scheme. + * + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author Laura Werner + * + * @since 4.0 + */ +public final class HttpHost implements Cloneable { + + /** The default scheme is "http". */ + public static final String DEFAULT_SCHEME_NAME = "http"; + + /** The host to use. */ + protected final String hostname; + + /** The lowercase host, for {@link #equals} and {@link #hashCode}. */ + protected final String lcHostname; + + + /** The port to use. */ + protected final int port; + + /** The scheme */ + protected final String schemeName; + + + /** + * Creates a new {@link HttpHost HttpHost}, specifying all values. + * Constructor for HttpHost. + * + * @param hostname the hostname (IP or DNS name) + * @param port the port number. + * <code>-1</code> indicates the scheme default port. + * @param scheme the name of the scheme. + * <code>null</code> indicates the + * {@link #DEFAULT_SCHEME_NAME default scheme} + */ + public HttpHost(final String hostname, int port, final String scheme) { + super(); + if (hostname == null) { + throw new IllegalArgumentException("Host name may not be null"); + } + this.hostname = hostname; + this.lcHostname = hostname.toLowerCase(Locale.ENGLISH); + if (scheme != null) { + this.schemeName = scheme.toLowerCase(Locale.ENGLISH); + } else { + this.schemeName = DEFAULT_SCHEME_NAME; + } + this.port = port; + } + + /** + * Creates a new {@link HttpHost HttpHost}, with default scheme. + * + * @param hostname the hostname (IP or DNS name) + * @param port the port number. + * <code>-1</code> indicates the scheme default port. + */ + public HttpHost(final String hostname, int port) { + this(hostname, port, null); + } + + /** + * Creates a new {@link HttpHost HttpHost}, with default scheme and port. + * + * @param hostname the hostname (IP or DNS name) + */ + public HttpHost(final String hostname) { + this(hostname, -1, null); + } + + /** + * Copy constructor for {@link HttpHost HttpHost}. + * + * @param httphost the HTTP host to copy details from + */ + public HttpHost (final HttpHost httphost) { + this(httphost.hostname, httphost.port, httphost.schemeName); + } + + /** + * Returns the host name. + * + * @return the host name (IP or DNS name) + */ + public String getHostName() { + return this.hostname; + } + + /** + * Returns the port. + * + * @return the host port, or <code>-1</code> if not set + */ + public int getPort() { + return this.port; + } + + /** + * Returns the scheme name. + * + * @return the scheme name + */ + public String getSchemeName() { + return this.schemeName; + } + + /** + * Return the host URI, as a string. + * + * @return the host URI + */ + public String toURI() { + CharArrayBuffer buffer = new CharArrayBuffer(32); + buffer.append(this.schemeName); + buffer.append("://"); + buffer.append(this.hostname); + if (this.port != -1) { + buffer.append(':'); + buffer.append(Integer.toString(this.port)); + } + return buffer.toString(); + } + + + /** + * Obtains the host string, without scheme prefix. + * + * @return the host string, for example <code>localhost:8080</code> + */ + public String toHostString() { + CharArrayBuffer buffer = new CharArrayBuffer(32); + buffer.append(this.hostname); + if (this.port != -1) { + buffer.append(':'); + buffer.append(Integer.toString(this.port)); + } + return buffer.toString(); + } + + + public String toString() { + return toURI(); + } + + + public boolean equals(final Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (obj instanceof HttpHost) { + HttpHost that = (HttpHost) obj; + return this.lcHostname.equals(that.lcHostname) + && this.port == that.port + && this.schemeName.equals(that.schemeName); + } else { + return false; + } + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.lcHostname); + hash = LangUtils.hashCode(hash, this.port); + hash = LangUtils.hashCode(hash, this.schemeName); + return hash; + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/org/apache/http/HttpInetConnection.java b/src/org/apache/http/HttpInetConnection.java new file mode 100644 index 0000000..32ac04a --- /dev/null +++ b/src/org/apache/http/HttpInetConnection.java @@ -0,0 +1,55 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpInetConnection.java $ + * $Revision: 613298 $ + * $Date: 2008-01-18 14:09:22 -0800 (Fri, 18 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.net.InetAddress; + +/** + * An HTTP connection over the Internet Protocol (IP). + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 613298 $ + * + * @since 4.0 + */ +public interface HttpInetConnection extends HttpConnection { + + InetAddress getLocalAddress(); + + int getLocalPort(); + + InetAddress getRemoteAddress(); + + int getRemotePort(); + +} diff --git a/src/org/apache/http/HttpMessage.java b/src/org/apache/http/HttpMessage.java new file mode 100644 index 0000000..d24f0b4 --- /dev/null +++ b/src/org/apache/http/HttpMessage.java @@ -0,0 +1,191 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpMessage.java $ + * $Revision: 610823 $ + * $Date: 2008-01-10 07:53:53 -0800 (Thu, 10 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import org.apache.http.params.HttpParams; + +/** + * A generic HTTP message. + * Holds what is common between requests and responses. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 610823 $ + * + * @since 4.0 + */ +public interface HttpMessage { + + /** + * Returns the protocol version this message is compatible with. + */ + ProtocolVersion getProtocolVersion(); + + /** + * Checks if a certain header is present in this message. Header values are + * ignored. + * + * @param name the header name to check for. + * @return true if at least one header with this name is present. + */ + boolean containsHeader(String name); + + /** + * Returns all the headers with a specified name of this message. Header values + * are ignored. Headers are orderd in the sequence they will be sent over a + * connection. + * + * @param name the name of the headers to return. + * @return the headers whose name property equals <code>name</code>. + */ + Header[] getHeaders(String name); + + /** + * Returns the first header with a specified name of this message. Header + * values are ignored. If there is more than one matching header in the + * message the first element of {@link #getHeaders(String)} is returned. + * If there is no matching header in the message <code>null</code> is + * returned. + * + * @param name the name of the header to return. + * @return the first header whose name property equals <code>name</code> + * or <code>null</code> if no such header could be found. + */ + Header getFirstHeader(String name); + + /** + * Returns the last header with a specified name of this message. Header values + * are ignored. If there is more than one matching header in the message the + * last element of {@link #getHeaders(String)} is returned. If there is no + * matching header in the message <code>null</code> is returned. + * + * @param name the name of the header to return. + * @return the last header whose name property equals <code>name</code>. + * or <code>null</code> if no such header could be found. + */ + Header getLastHeader(String name); + + /** + * Returns all the headers of this message. Headers are orderd in the sequence + * they will be sent over a connection. + * + * @return all the headers of this message + */ + Header[] getAllHeaders(); + + /** + * Adds a header to this message. The header will be appended to the end of + * the list. + * + * @param header the header to append. + */ + void addHeader(Header header); + + /** + * Adds a header to this message. The header will be appended to the end of + * the list. + * + * @param name the name of the header. + * @param value the value of the header. + */ + void addHeader(String name, String value); + + /** + * Overwrites the first header with the same name. The new header will be appended to + * the end of the list, if no header with the given name can be found. + * + * @param header the header to set. + */ + void setHeader(Header header); + + /** + * Overwrites the first header with the same name. The new header will be appended to + * the end of the list, if no header with the given name can be found. + * + * @param name the name of the header. + * @param value the value of the header. + */ + void setHeader(String name, String value); + + /** + * Overwrites all the headers in the message. + * + * @param headers the array of headers to set. + */ + void setHeaders(Header[] headers); + + /** + * Removes a header from this message. + * + * @param header the header to remove. + */ + void removeHeader(Header header); + + /** + * Removes all headers with a certain name from this message. + * + * @param name The name of the headers to remove. + */ + void removeHeaders(String name); + + /** + * Returns an iterator of all the headers. + * + * @return Iterator that returns Header objects in the sequence they are + * sent over a connection. + */ + HeaderIterator headerIterator(); + + /** + * Returns an iterator of the headers with a given name. + * + * @param name the name of the headers over which to iterate, or + * <code>null</code> for all headers + * + * @return Iterator that returns Header objects with the argument name + * in the sequence they are sent over a connection. + */ + HeaderIterator headerIterator(String name); + + /** + * Returns the parameters effective for this message as set by + * {@link #setParams(HttpParams)}. + */ + HttpParams getParams(); + + /** + * Provides parameters to be used for the processing of this message. + * @param params the parameters + */ + void setParams(HttpParams params); + +} diff --git a/src/org/apache/http/HttpRequest.java b/src/org/apache/http/HttpRequest.java new file mode 100644 index 0000000..8558a97 --- /dev/null +++ b/src/org/apache/http/HttpRequest.java @@ -0,0 +1,51 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpRequest.java $ + * $Revision: 528428 $ + * $Date: 2007-04-13 03:26:04 -0700 (Fri, 13 Apr 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * An HTTP request. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 528428 $ + * + * @since 4.0 + */ +public interface HttpRequest extends HttpMessage { + + /** + * Returns the request line of this request. + * @return the request line. + */ + RequestLine getRequestLine(); + +} diff --git a/src/org/apache/http/HttpRequestFactory.java b/src/org/apache/http/HttpRequestFactory.java new file mode 100644 index 0000000..d385127 --- /dev/null +++ b/src/org/apache/http/HttpRequestFactory.java @@ -0,0 +1,51 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpRequestFactory.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * A factory for {@link HttpRequest HttpRequest} objects. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public interface HttpRequestFactory { + + HttpRequest newHttpRequest(RequestLine requestline) + throws MethodNotSupportedException; + + HttpRequest newHttpRequest(String method, String uri) + throws MethodNotSupportedException; + +} diff --git a/src/org/apache/http/HttpRequestInterceptor.java b/src/org/apache/http/HttpRequestInterceptor.java new file mode 100644 index 0000000..db2194f --- /dev/null +++ b/src/org/apache/http/HttpRequestInterceptor.java @@ -0,0 +1,69 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpRequestInterceptor.java $ + * $Revision: 618367 $ + * $Date: 2008-02-04 10:26:06 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +import org.apache.http.protocol.HttpContext; + + +/** + * Processes a request. + * Provides the ability to process a request before it is sent + * to the server or after it has received on the server side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 618367 $ + * + * @since 4.0 + */ +public interface HttpRequestInterceptor { + + /** + * Processes a request. + * On the client side, this step is performed before the request is + * sent to the server. On the server side, this step is performed + * on incoming messages before the message body is evaluated. + * + * @param request the request to preprocess + * @param context the context for the request + * + * @throws IOException in case of an IO problem + * @throws HttpException in case of a protocol or other problem + */ + void process(HttpRequest request, HttpContext context) + throws HttpException, IOException; + +} diff --git a/src/org/apache/http/HttpResponse.java b/src/org/apache/http/HttpResponse.java new file mode 100644 index 0000000..f232f86 --- /dev/null +++ b/src/org/apache/http/HttpResponse.java @@ -0,0 +1,161 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpResponse.java $ + * $Revision: 652956 $ + * $Date: 2008-05-02 17:13:05 -0700 (Fri, 02 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + + +import java.util.Locale; + + +/** + * An HTTP response. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 652956 $ + * + * @since 4.0 + */ +public interface HttpResponse extends HttpMessage { + + /** + * Obtains the status line of this response. + * The status line can be set using one of the + * {@link #setStatusLine setStatusLine} methods, + * or it can be initialized in a constructor. + * + * @return the status line, or <code>null</code> if not yet set + */ + StatusLine getStatusLine(); + + /** + * Sets the status line of this response. + * + * @param statusline the status line of this response + */ + void setStatusLine(StatusLine statusline); + + /** + * Sets the status line of this response. + * The reason phrase will be determined based on the current + * {@link #getLocale locale}. + * + * @param ver the HTTP version + * @param code the status code + */ + void setStatusLine(ProtocolVersion ver, int code); + + /** + * Sets the status line of this response with a reason phrase. + * + * @param ver the HTTP version + * @param code the status code + * @param reason the reason phrase, or <code>null</code> to omit + */ + void setStatusLine(ProtocolVersion ver, int code, String reason); + + /** + * Updates the status line of this response with a new status code. + * The status line can only be updated if it is available. It must + * have been set either explicitly or in a constructor. + * <br/> + * The reason phrase will be updated according to the new status code, + * based on the current {@link #getLocale locale}. It can be set + * explicitly using {@link #setReasonPhrase setReasonPhrase}. + * + * @param code the HTTP status code. + * + * @throws IllegalStateException + * if the status line has not be set + * + * @see HttpStatus + * @see #setStatusLine(StatusLine) + * @see #setStatusLine(ProtocolVersion,int) + */ + void setStatusCode(int code) + throws IllegalStateException; + + /** + * Updates the status line of this response with a new reason phrase. + * The status line can only be updated if it is available. It must + * have been set either explicitly or in a constructor. + * + * @param reason the new reason phrase as a single-line string, or + * <code>null</code> to unset the reason phrase + * + * @throws IllegalStateException + * if the status line has not be set + * + * @see #setStatusLine(StatusLine) + * @see #setStatusLine(ProtocolVersion,int) + */ + void setReasonPhrase(String reason) + throws IllegalStateException; + + /** + * Obtains the message entity of this response, if any. + * The entity is provided by calling {@link #setEntity setEntity}. + * + * @return the response entity, or + * <code>null</code> if there is none + */ + HttpEntity getEntity(); + + /** + * Associates a response entity with this response. + * + * @param entity the entity to associate with this response, or + * <code>null</code> to unset + */ + void setEntity(HttpEntity entity); + + /** + * Obtains the locale of this response. + * The locale is used to determine the reason phrase + * for the {@link #setStatusCode status code}. + * It can be changed using {@link #setLocale setLocale}. + * + * @return the locale of this response, never <code>null</code> + */ + Locale getLocale(); + + /** + * Changes the locale of this response. + * If there is a status line, it's reason phrase will be updated + * according to the status code and new locale. + * + * @param loc the new locale + * + * @see #getLocale getLocale + * @see #setStatusCode setStatusCode + */ + void setLocale(Locale loc); +} diff --git a/src/org/apache/http/HttpResponseFactory.java b/src/org/apache/http/HttpResponseFactory.java new file mode 100644 index 0000000..3ed06ff --- /dev/null +++ b/src/org/apache/http/HttpResponseFactory.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpResponseFactory.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import org.apache.http.protocol.HttpContext; + + +/** + * A factory for {@link HttpResponse HttpResponse} objects. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public interface HttpResponseFactory { + + /** + * Creates a new response from status line elements. + * + * @param ver the protocol version + * @param status the status code + * @param context the context from which to determine the locale + * for looking up a reason phrase to the status code, or + * <code>null</code> to use the default locale + * + * @return the new response with an initialized status line + */ + HttpResponse newHttpResponse(ProtocolVersion ver, int status, + HttpContext context); + + /** + * Creates a new response from a status line. + * + * @param statusline the status line + * @param context the context from which to determine the locale + * for looking up a reason phrase if the status code + * is updated, or + * <code>null</code> to use the default locale + * + * @return the new response with the argument status line + */ + HttpResponse newHttpResponse(StatusLine statusline, + HttpContext context); + +} diff --git a/src/org/apache/http/HttpResponseInterceptor.java b/src/org/apache/http/HttpResponseInterceptor.java new file mode 100644 index 0000000..cae1526 --- /dev/null +++ b/src/org/apache/http/HttpResponseInterceptor.java @@ -0,0 +1,68 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpResponseInterceptor.java $ + * $Revision: 618367 $ + * $Date: 2008-02-04 10:26:06 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +import org.apache.http.protocol.HttpContext; + +/** + * Processes a response. + * Provides the ability to process a response before it is sent + * to the client or after it has been received on the client side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 618367 $ + * + * @since 4.0 + */ +public interface HttpResponseInterceptor { + + /** + * Processes a response. + * On the server side, this step is performed before the response is + * sent to the client. On the client side, this step is performed + * on incoming messages before the message body is evaluated. + * + * @param response the response to postprocess + * @param context the context for the request + * + * @throws IOException in case of an IO problem + * @throws HttpException in case of a protocol or other problem + */ + void process(HttpResponse response, HttpContext context) + throws HttpException, IOException; + +} diff --git a/src/org/apache/http/HttpServerConnection.java b/src/org/apache/http/HttpServerConnection.java new file mode 100644 index 0000000..8cea544 --- /dev/null +++ b/src/org/apache/http/HttpServerConnection.java @@ -0,0 +1,96 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpServerConnection.java $ + * $Revision: 542199 $ + * $Date: 2007-05-28 04:23:46 -0700 (Mon, 28 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +/** + * An HTTP connection for use on the server side. + * Requests are received, responses are sent. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 542199 $ + * + * @since 4.0 + */ +public interface HttpServerConnection extends HttpConnection { + + /** + * Receives the request line and all headers available from this connection. + * The caller should examine the returned request and decide if to receive a + * request entity as well. + * + * @return a new HttpRequest object whose request line and headers are + * initialized. + * @throws HttpException + * @throws IOException + */ + HttpRequest receiveRequestHeader() + throws HttpException, IOException; + + /** + * Receives the next request entity available from this connection and attaches it to + * an existing request. + * @param request the request to attach the entity to. + * @throws HttpException + * @throws IOException + */ + void receiveRequestEntity(HttpEntityEnclosingRequest request) + throws HttpException, IOException; + + /** + * Sends the response line and headers of a response over this connection. + * @param response the response whose headers to send. + * @throws HttpException + * @throws IOException + */ + void sendResponseHeader(HttpResponse response) + throws HttpException, IOException; + + /** + * Sends the response entity of a response over this connection. + * @param response the response whose entity to send. + * @throws HttpException + * @throws IOException + */ + void sendResponseEntity(HttpResponse response) + throws HttpException, IOException; + + /** + * Sends all pending buffered data over this connection. + * @throws IOException + */ + void flush() + throws IOException; + +} diff --git a/src/org/apache/http/HttpStatus.java b/src/org/apache/http/HttpStatus.java new file mode 100644 index 0000000..f8f7f5d --- /dev/null +++ b/src/org/apache/http/HttpStatus.java @@ -0,0 +1,182 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpStatus.java $ + * $Revision: 503381 $ + * $Date: 2007-02-04 02:59:10 -0800 (Sun, 04 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * Constants enumerating the HTTP status codes. + * All status codes defined in RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), and + * RFC2518 (WebDAV) are listed. + * + * @see StatusLine + * @author Unascribed + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * + * @version $Revision: 503381 $ + */ +public interface HttpStatus { + + // --- 1xx Informational --- + + /** <tt>100 Continue</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_CONTINUE = 100; + /** <tt>101 Switching Protocols</tt> (HTTP/1.1 - RFC 2616)*/ + public static final int SC_SWITCHING_PROTOCOLS = 101; + /** <tt>102 Processing</tt> (WebDAV - RFC 2518) */ + public static final int SC_PROCESSING = 102; + + // --- 2xx Success --- + + /** <tt>200 OK</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_OK = 200; + /** <tt>201 Created</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_CREATED = 201; + /** <tt>202 Accepted</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_ACCEPTED = 202; + /** <tt>203 Non Authoritative Information</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203; + /** <tt>204 No Content</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_NO_CONTENT = 204; + /** <tt>205 Reset Content</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_RESET_CONTENT = 205; + /** <tt>206 Partial Content</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_PARTIAL_CONTENT = 206; + /** + * <tt>207 Multi-Status</tt> (WebDAV - RFC 2518) or <tt>207 Partial Update + * OK</tt> (HTTP/1.1 - draft-ietf-http-v11-spec-rev-01?) + */ + public static final int SC_MULTI_STATUS = 207; + + // --- 3xx Redirection --- + + /** <tt>300 Mutliple Choices</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_MULTIPLE_CHOICES = 300; + /** <tt>301 Moved Permanently</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_MOVED_PERMANENTLY = 301; + /** <tt>302 Moved Temporarily</tt> (Sometimes <tt>Found</tt>) (HTTP/1.0 - RFC 1945) */ + public static final int SC_MOVED_TEMPORARILY = 302; + /** <tt>303 See Other</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_SEE_OTHER = 303; + /** <tt>304 Not Modified</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_NOT_MODIFIED = 304; + /** <tt>305 Use Proxy</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_USE_PROXY = 305; + /** <tt>307 Temporary Redirect</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_TEMPORARY_REDIRECT = 307; + + // --- 4xx Client Error --- + + /** <tt>400 Bad Request</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_BAD_REQUEST = 400; + /** <tt>401 Unauthorized</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_UNAUTHORIZED = 401; + /** <tt>402 Payment Required</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_PAYMENT_REQUIRED = 402; + /** <tt>403 Forbidden</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_FORBIDDEN = 403; + /** <tt>404 Not Found</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_NOT_FOUND = 404; + /** <tt>405 Method Not Allowed</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_METHOD_NOT_ALLOWED = 405; + /** <tt>406 Not Acceptable</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_NOT_ACCEPTABLE = 406; + /** <tt>407 Proxy Authentication Required</tt> (HTTP/1.1 - RFC 2616)*/ + public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407; + /** <tt>408 Request Timeout</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_REQUEST_TIMEOUT = 408; + /** <tt>409 Conflict</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_CONFLICT = 409; + /** <tt>410 Gone</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_GONE = 410; + /** <tt>411 Length Required</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_LENGTH_REQUIRED = 411; + /** <tt>412 Precondition Failed</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_PRECONDITION_FAILED = 412; + /** <tt>413 Request Entity Too Large</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_REQUEST_TOO_LONG = 413; + /** <tt>414 Request-URI Too Long</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_REQUEST_URI_TOO_LONG = 414; + /** <tt>415 Unsupported Media Type</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415; + /** <tt>416 Requested Range Not Satisfiable</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + /** <tt>417 Expectation Failed</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_EXPECTATION_FAILED = 417; + + /** + * Static constant for a 418 error. + * <tt>418 Unprocessable Entity</tt> (WebDAV drafts?) + * or <tt>418 Reauthentication Required</tt> (HTTP/1.1 drafts?) + */ + // not used + // public static final int SC_UNPROCESSABLE_ENTITY = 418; + + /** + * Static constant for a 419 error. + * <tt>419 Insufficient Space on Resource</tt> + * (WebDAV - draft-ietf-webdav-protocol-05?) + * or <tt>419 Proxy Reauthentication Required</tt> + * (HTTP/1.1 drafts?) + */ + public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419; + /** + * Static constant for a 420 error. + * <tt>420 Method Failure</tt> + * (WebDAV - draft-ietf-webdav-protocol-05?) + */ + public static final int SC_METHOD_FAILURE = 420; + /** <tt>422 Unprocessable Entity</tt> (WebDAV - RFC 2518) */ + public static final int SC_UNPROCESSABLE_ENTITY = 422; + /** <tt>423 Locked</tt> (WebDAV - RFC 2518) */ + public static final int SC_LOCKED = 423; + /** <tt>424 Failed Dependency</tt> (WebDAV - RFC 2518) */ + public static final int SC_FAILED_DEPENDENCY = 424; + + // --- 5xx Server Error --- + + /** <tt>500 Server Error</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_INTERNAL_SERVER_ERROR = 500; + /** <tt>501 Not Implemented</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_NOT_IMPLEMENTED = 501; + /** <tt>502 Bad Gateway</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_BAD_GATEWAY = 502; + /** <tt>503 Service Unavailable</tt> (HTTP/1.0 - RFC 1945) */ + public static final int SC_SERVICE_UNAVAILABLE = 503; + /** <tt>504 Gateway Timeout</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_GATEWAY_TIMEOUT = 504; + /** <tt>505 HTTP Version Not Supported</tt> (HTTP/1.1 - RFC 2616) */ + public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505; + + /** <tt>507 Insufficient Storage</tt> (WebDAV - RFC 2518) */ + public static final int SC_INSUFFICIENT_STORAGE = 507; + +} diff --git a/src/org/apache/http/HttpVersion.java b/src/org/apache/http/HttpVersion.java new file mode 100644 index 0000000..243f199 --- /dev/null +++ b/src/org/apache/http/HttpVersion.java @@ -0,0 +1,104 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/HttpVersion.java $ + * $Revision: 609106 $ + * $Date: 2008-01-05 01:15:42 -0800 (Sat, 05 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.Serializable; + +/** + * Represents an HTTP version, as specified in RFC 2616. + * + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 609106 $ $Date: 2008-01-05 01:15:42 -0800 (Sat, 05 Jan 2008) $ + */ +public final class HttpVersion extends ProtocolVersion + implements Serializable { + + private static final long serialVersionUID = -5856653513894415344L; + + /** The protocol name. */ + public static final String HTTP = "HTTP"; + + /** HTTP protocol version 0.9 */ + public static final HttpVersion HTTP_0_9 = new HttpVersion(0, 9); + + /** HTTP protocol version 1.0 */ + public static final HttpVersion HTTP_1_0 = new HttpVersion(1, 0); + + /** HTTP protocol version 1.1 */ + public static final HttpVersion HTTP_1_1 = new HttpVersion(1, 1); + + + /** + * Create an HTTP protocol version designator. + * + * @param major the major version number of the HTTP protocol + * @param minor the minor version number of the HTTP protocol + * + * @throws IllegalArgumentException if either major or minor version number is negative + */ + public HttpVersion(int major, int minor) { + super(HTTP, major, minor); + } + + + /** + * Obtains a specific HTTP version. + * + * @param major the major version + * @param minor the minor version + * + * @return an instance of {@link HttpVersion} with the argument version + */ + public ProtocolVersion forVersion(int major, int minor) { + + if ((major == this.major) && (minor == this.minor)) { + return this; + } + + if (major == 1) { + if (minor == 0) { + return HTTP_1_0; + } + if (minor == 1) { + return HTTP_1_1; + } + } + if ((major == 0) && (minor == 9)) { + return HTTP_0_9; + } + + // argument checking is done in the constructor + return new HttpVersion(major, minor); + } + +} diff --git a/src/org/apache/http/MalformedChunkCodingException.java b/src/org/apache/http/MalformedChunkCodingException.java new file mode 100644 index 0000000..2267a2e --- /dev/null +++ b/src/org/apache/http/MalformedChunkCodingException.java @@ -0,0 +1,59 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/MalformedChunkCodingException.java $ + * $Revision: 609106 $ + * $Date: 2008-01-05 01:15:42 -0800 (Sat, 05 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +/** + * Signals a malformed chunked stream. + */ +public class MalformedChunkCodingException extends IOException { + + private static final long serialVersionUID = 2158560246948994524L; + + /** + * Creates a MalformedChunkCodingException without a detail message. + */ + public MalformedChunkCodingException() { + super(); + } + + /** + * Creates a MalformedChunkCodingException with the specified detail message. + * + * @param message The exception detail message + */ + public MalformedChunkCodingException(final String message) { + super(message); + } + +} diff --git a/src/org/apache/http/MethodNotSupportedException.java b/src/org/apache/http/MethodNotSupportedException.java new file mode 100644 index 0000000..3ccf72d --- /dev/null +++ b/src/org/apache/http/MethodNotSupportedException.java @@ -0,0 +1,67 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/MethodNotSupportedException.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + + +/** + * Indicates that an HTTP method is not supported. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public class MethodNotSupportedException extends HttpException { + + private static final long serialVersionUID = 3365359036840171201L; + + /** + * Creates a new MethodNotSupportedException with the specified detail message. + * + * @param message The exception detail message + */ + public MethodNotSupportedException(final String message) { + super(message); + } + + /** + * Creates a new MethodNotSupportedException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public MethodNotSupportedException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/NameValuePair.java b/src/org/apache/http/NameValuePair.java new file mode 100644 index 0000000..1ab861a --- /dev/null +++ b/src/org/apache/http/NameValuePair.java @@ -0,0 +1,108 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/NameValuePair.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * A simple class encapsulating an attribute/value pair. + * <p> + * This class comforms to the generic grammar and formatting rules outlined in the + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2">Section 2.2</a> + * and + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a> + * of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a> + * </p> + * <h>2.2 Basic Rules</h> + * <p> + * The following rules are used throughout this specification to describe basic parsing constructs. + * The US-ASCII coded character set is defined by ANSI X3.4-1986. + * </p> + * <pre> + * OCTET = <any 8-bit sequence of data> + * CHAR = <any US-ASCII character (octets 0 - 127)> + * UPALPHA = <any US-ASCII uppercase letter "A".."Z"> + * LOALPHA = <any US-ASCII lowercase letter "a".."z"> + * ALPHA = UPALPHA | LOALPHA + * DIGIT = <any US-ASCII digit "0".."9"> + * CTL = <any US-ASCII control character + * (octets 0 - 31) and DEL (127)> + * CR = <US-ASCII CR, carriage return (13)> + * LF = <US-ASCII LF, linefeed (10)> + * SP = <US-ASCII SP, space (32)> + * HT = <US-ASCII HT, horizontal-tab (9)> + * <"> = <US-ASCII double-quote mark (34)> + * </pre> + * <p> + * Many HTTP/1.1 header field values consist of words separated by LWS or special + * characters. These special characters MUST be in a quoted string to be used within + * a parameter value (as defined in section 3.6). + * <p> + * <pre> + * token = 1*<any CHAR except CTLs or separators> + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + * </pre> + * <p> + * A string of text is parsed as a single word if it is quoted using double-quote marks. + * </p> + * <pre> + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = <any TEXT except <">> + * </pre> + * <p> + * The backslash character ("\") MAY be used as a single-character quoting mechanism only + * within quoted-string and comment constructs. + * </p> + * <pre> + * quoted-pair = "\" CHAR + * </pre> + * <h>3.6 Transfer Codings</h> + * <p> + * Parameters are in the form of attribute/value pairs. + * </p> + * <pre> + * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + * </pre> + * + * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a> + * + */ +public interface NameValuePair { + + String getName(); + + String getValue(); + +} diff --git a/src/org/apache/http/NoHttpResponseException.java b/src/org/apache/http/NoHttpResponseException.java new file mode 100644 index 0000000..a02ef5a --- /dev/null +++ b/src/org/apache/http/NoHttpResponseException.java @@ -0,0 +1,58 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/NoHttpResponseException.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.IOException; + +/** + * <p> + * Signals that the target server failed to respond with a valid HTTP response. + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + */ +public class NoHttpResponseException extends IOException { + + private static final long serialVersionUID = -7658940387386078766L; + + /** + * Creates a new NoHttpResponseException with the specified detail message. + * + * @param message exception message + */ + public NoHttpResponseException(String message) { + super(message); + } + +} diff --git a/src/org/apache/http/ParseException.java b/src/org/apache/http/ParseException.java new file mode 100644 index 0000000..97083b2 --- /dev/null +++ b/src/org/apache/http/ParseException.java @@ -0,0 +1,65 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/ParseException.java $ + * $Revision: 609106 $ + * $Date: 2008-01-05 01:15:42 -0800 (Sat, 05 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * Indicates a parse error. + * Parse errors when receiving a message will typically trigger + * {@link ProtocolException}. Parse errors that do not occur during + * protocol execution may be handled differently. + * This is an unchecked exceptions, since there are cases where + * the data to be parsed has been generated and is therefore + * known to be parseable. + * + * @since 4.0 + */ +public class ParseException extends RuntimeException { + + private static final long serialVersionUID = -7288819855864183578L; + + /** + * Creates a {@link ParseException} without details. + */ + public ParseException() { + super(); + } + + /** + * Creates a {@link ParseException} with a detail message. + * + * @param message the exception detail message, or <code>null</code> + */ + public ParseException(String message) { + super(message); + } + +} diff --git a/src/org/apache/http/ProtocolException.java b/src/org/apache/http/ProtocolException.java new file mode 100644 index 0000000..b4c34ee --- /dev/null +++ b/src/org/apache/http/ProtocolException.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/ProtocolException.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * Signals that an HTTP protocol violation has occurred. + * For example a malformed status line or headers, a missing message body, etc. + * + * @author <a href="mailto:laura@lwerner.org">Laura Werner</a> + * + * @since 4.0 + */ +public class ProtocolException extends HttpException { + + private static final long serialVersionUID = -2143571074341228994L; + + /** + * Creates a new ProtocolException with a <tt>null</tt> detail message. + */ + public ProtocolException() { + super(); + } + + /** + * Creates a new ProtocolException with the specified detail message. + * + * @param message The exception detail message + */ + public ProtocolException(String message) { + super(message); + } + + /** + * Creates a new ProtocolException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public ProtocolException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/ProtocolVersion.java b/src/org/apache/http/ProtocolVersion.java new file mode 100644 index 0000000..ced76a5 --- /dev/null +++ b/src/org/apache/http/ProtocolVersion.java @@ -0,0 +1,287 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/ProtocolVersion.java $ + * $Revision: 609106 $ + * $Date: 2008-01-05 01:15:42 -0800 (Sat, 05 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.io.Serializable; +import org.apache.http.util.CharArrayBuffer; + + +/** + * Represents a protocol version, as specified in RFC 2616. + * RFC 2616 specifies only HTTP versions, like "HTTP/1.1" and "HTTP/1.0". + * RFC 3261 specifies a message format that is identical to HTTP except + * for the protocol name. It defines a protocol version "SIP/2.0". + * There are some nitty-gritty differences between the interpretation + * of versions in HTTP and SIP. In those cases, HTTP takes precedence. + * <p> + * This class defines a protocol version as a combination of + * protocol name, major version number, and minor version number. + * Note that {@link #equals} and {@link #hashCode} are defined as + * final here, they cannot be overridden in derived classes. + * + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 609106 $ + */ +public class ProtocolVersion implements Serializable, Cloneable { + + private static final long serialVersionUID = 8950662842175091068L; + + + /** Name of the protocol. */ + protected final String protocol; + + /** Major version number of the protocol */ + protected final int major; + + /** Minor version number of the protocol */ + protected final int minor; + + + /** + * Create a protocol version designator. + * + * @param protocol the name of the protocol, for example "HTTP" + * @param major the major version number of the protocol + * @param minor the minor version number of the protocol + */ + public ProtocolVersion(String protocol, int major, int minor) { + if (protocol == null) { + throw new IllegalArgumentException + ("Protocol name must not be null."); + } + if (major < 0) { + throw new IllegalArgumentException + ("Protocol major version number must not be negative."); + } + if (minor < 0) { + throw new IllegalArgumentException + ("Protocol minor version number may not be negative"); + } + this.protocol = protocol; + this.major = major; + this.minor = minor; + } + + /** + * Returns the name of the protocol. + * + * @return the protocol name + */ + public final String getProtocol() { + return protocol; + } + + /** + * Returns the major version number of the protocol. + * + * @return the major version number. + */ + public final int getMajor() { + return major; + } + + /** + * Returns the minor version number of the HTTP protocol. + * + * @return the minor version number. + */ + public final int getMinor() { + return minor; + } + + + /** + * Obtains a specific version of this protocol. + * This can be used by derived classes to instantiate themselves instead + * of the base class, and to define constants for commonly used versions. + * <br/> + * The default implementation in this class returns <code>this</code> + * if the version matches, and creates a new {@link ProtocolVersion} + * otherwise. + * + * @param major the major version + * @param minor the minor version + * + * @return a protocol version with the same protocol name + * and the argument version + */ + public ProtocolVersion forVersion(int major, int minor) { + + if ((major == this.major) && (minor == this.minor)) { + return this; + } + + // argument checking is done in the constructor + return new ProtocolVersion(this.protocol, major, minor); + } + + + /** + * Obtains a hash code consistent with {@link #equals}. + * + * @return the hashcode of this protocol version + */ + public final int hashCode() { + return this.protocol.hashCode() ^ (this.major * 100000) ^ this.minor; + } + + + /** + * Checks equality of this protocol version with an object. + * The object is equal if it is a protocl version with the same + * protocol name, major version number, and minor version number. + * The specific class of the object is <i>not</i> relevant, + * instances of derived classes with identical attributes are + * equal to instances of the base class and vice versa. + * + * @param obj the object to compare with + * + * @return <code>true</code> if the argument is the same protocol version, + * <code>false</code> otherwise + */ + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ProtocolVersion)) { + return false; + } + ProtocolVersion that = (ProtocolVersion) obj; + + return ((this.protocol.equals(that.protocol)) && + (this.major == that.major) && + (this.minor == that.minor)); + } + + + /** + * Checks whether this protocol can be compared to another one. + * Only protocol versions with the same protocol name can be + * {@link #compareToVersion compared}. + * + * @param that the protocol version to consider + * + * @return <code>true</code> if {@link #compareToVersion compareToVersion} + * can be called with the argument, <code>false</code> otherwise + */ + public boolean isComparable(ProtocolVersion that) { + return (that != null) && this.protocol.equals(that.protocol); + } + + + /** + * Compares this protocol version with another one. + * Only protocol versions with the same protocol name can be compared. + * This method does <i>not</i> define a total ordering, as it would be + * required for {@link java.lang.Comparable}. + * + * @param that the protocl version to compare with + * + * @return a negative integer, zero, or a positive integer + * as this version is less than, equal to, or greater than + * the argument version. + * + * @throws IllegalArgumentException + * if the argument has a different protocol name than this object, + * or if the argument is <code>null</code> + */ + public int compareToVersion(ProtocolVersion that) { + if (that == null) { + throw new IllegalArgumentException + ("Protocol version must not be null."); + } + if (!this.protocol.equals(that.protocol)) { + throw new IllegalArgumentException + ("Versions for different protocols cannot be compared. " + + this + " " + that); + } + + int delta = getMajor() - that.getMajor(); + if (delta == 0) { + delta = getMinor() - that.getMinor(); + } + return delta; + } + + + /** + * Tests if this protocol version is greater or equal to the given one. + * + * @param version the version against which to check this version + * + * @return <code>true</code> if this protocol version is + * {@link #isComparable comparable} to the argument + * and {@link #compareToVersion compares} as greater or equal, + * <code>false</code> otherwise + */ + public final boolean greaterEquals(ProtocolVersion version) { + return isComparable(version) && (compareToVersion(version) >= 0); + } + + + /** + * Tests if this protocol version is less or equal to the given one. + * + * @param version the version against which to check this version + * + * @return <code>true</code> if this protocol version is + * {@link #isComparable comparable} to the argument + * and {@link #compareToVersion compares} as less or equal, + * <code>false</code> otherwise + */ + public final boolean lessEquals(ProtocolVersion version) { + return isComparable(version) && (compareToVersion(version) <= 0); + } + + + /** + * Converts this protocol version to a string. + * + * @return a protocol version string, like "HTTP/1.1" + */ + public String toString() { + CharArrayBuffer buffer = new CharArrayBuffer(16); + buffer.append(this.protocol); + buffer.append('/'); + buffer.append(Integer.toString(this.major)); + buffer.append('.'); + buffer.append(Integer.toString(this.minor)); + return buffer.toString(); + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/org/apache/http/ReasonPhraseCatalog.java b/src/org/apache/http/ReasonPhraseCatalog.java new file mode 100644 index 0000000..12ad6d9 --- /dev/null +++ b/src/org/apache/http/ReasonPhraseCatalog.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/ReasonPhraseCatalog.java $ + * $Revision: 505744 $ + * $Date: 2007-02-10 10:58:45 -0800 (Sat, 10 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import java.util.Locale; + + +/** + * Interface for obtaining reason phrases for HTTP status codes. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 505744 $ + * + * @since 4.0 + */ +public interface ReasonPhraseCatalog { + + /** + * Obtains the reason phrase for a status code. + * The optional context allows for catalogs that detect + * the language for the reason phrase. + * + * @param status the status code, in the range 100-599 + * @param loc the preferred locale for the reason phrase + * + * @return the reason phrase, or <code>null</code> if unknown + */ + public String getReason(int status, Locale loc) + ; + +} diff --git a/src/org/apache/http/RequestLine.java b/src/org/apache/http/RequestLine.java new file mode 100644 index 0000000..e865929 --- /dev/null +++ b/src/org/apache/http/RequestLine.java @@ -0,0 +1,53 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/RequestLine.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * The first line of an {@link HttpRequest HttpRequest}. + * It contains the method, URI, and HTTP version of the request. + * For details, see RFC 2616. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public interface RequestLine { + + String getMethod(); + + ProtocolVersion getProtocolVersion(); + + String getUri(); + +} diff --git a/src/org/apache/http/StatusLine.java b/src/org/apache/http/StatusLine.java new file mode 100644 index 0000000..aa8a3bc --- /dev/null +++ b/src/org/apache/http/StatusLine.java @@ -0,0 +1,55 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/StatusLine.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +/** + * Represents a status line as returned from a HTTP server. + * See <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>, + * section 6.1. + * Implementations are expected to be thread safe. + * + * @see HttpStatus + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @version $Id: StatusLine.java 573864 2007-09-08 15:53:25Z rolandw $ + * + * @since 4.0 + */ +public interface StatusLine { + + ProtocolVersion getProtocolVersion(); + + int getStatusCode(); + + String getReasonPhrase(); + +} diff --git a/src/org/apache/http/TokenIterator.java b/src/org/apache/http/TokenIterator.java new file mode 100644 index 0000000..bfe3473 --- /dev/null +++ b/src/org/apache/http/TokenIterator.java @@ -0,0 +1,67 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/TokenIterator.java $ + * $Revision: 601000 $ + * $Date: 2007-12-04 09:03:49 -0800 (Tue, 04 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + + +import java.util.Iterator; + + +/** + * An iterator for {@link String} tokens. + * This interface is designed as a complement to + * {@link HeaderElementIterator}, in cases where the items + * are plain strings rather than full header elements. + * + * @version $Revision: 601000 $ + */ +public interface TokenIterator extends Iterator { + + /** + * Indicates whether there is another token in this iteration. + * + * @return <code>true</code> if there is another token, + * <code>false</code> otherwise + */ + boolean hasNext() + ; + + + /** + * Obtains the next token from this iteration. + * This method should only be called while {@link #hasNext hasNext} + * is true. + * + * @return the next token in this iteration + */ + String nextToken() + ; +} diff --git a/src/org/apache/http/UnsupportedHttpVersionException.java b/src/org/apache/http/UnsupportedHttpVersionException.java new file mode 100644 index 0000000..716d7a5 --- /dev/null +++ b/src/org/apache/http/UnsupportedHttpVersionException.java @@ -0,0 +1,64 @@ +/* + * $HeadURL:https://svn.apache.org/repos/asf/jakarta/httpcomponents/trunk/coyote-httpconnector/src/java/org/apache/http/tcconnector/UnsupportedHttpVersionException.java $ + * $Revision:379772 $ + * $Date:2006-02-22 14:52:29 +0100 (Wed, 22 Feb 2006) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http; + +import org.apache.http.ProtocolException; + +/** + * Indicates an unsupported version of the HTTP protocol. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision:379772 $ + */ +public class UnsupportedHttpVersionException extends ProtocolException { + + private static final long serialVersionUID = -1348448090193107031L; + + + /** + * Creates an exception without a detail message. + */ + public UnsupportedHttpVersionException() { + super(); + } + + /** + * Creates an exception with the specified detail message. + * + * @param message The exception detail message + */ + public UnsupportedHttpVersionException(final String message) { + super(message); + } + +} diff --git a/src/org/apache/http/auth/AUTH.java b/src/org/apache/http/auth/AUTH.java new file mode 100644 index 0000000..8ab6dc2 --- /dev/null +++ b/src/org/apache/http/auth/AUTH.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/AUTH.java $ + * $Revision: 618365 $ + * $Date: 2008-02-04 10:20:08 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +/** + * Constants and static helpers related to the HTTP authentication. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public final class AUTH { + + /** + * The www authenticate challange header. + */ + public static final String WWW_AUTH = "WWW-Authenticate"; + + /** + * The www authenticate response header. + */ + public static final String WWW_AUTH_RESP = "Authorization"; + + /** + * The proxy authenticate challange header. + */ + public static final String PROXY_AUTH = "Proxy-Authenticate"; + + /** + * The proxy authenticate response header. + */ + public static final String PROXY_AUTH_RESP = "Proxy-Authorization"; + + private AUTH() { + } + +} diff --git a/src/org/apache/http/auth/AuthScheme.java b/src/org/apache/http/auth/AuthScheme.java new file mode 100644 index 0000000..bdaebdb --- /dev/null +++ b/src/org/apache/http/auth/AuthScheme.java @@ -0,0 +1,140 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/AuthScheme.java $ + * $Revision: 537144 $ + * $Date: 2007-05-11 02:30:13 -0700 (Fri, 11 May 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import org.apache.http.Header; +import org.apache.http.HttpRequest; + +/** + * <p> + * This interface represents an abstract challenge-response oriented + * authentication scheme. + * </p> + * <p> + * An authentication scheme should be able to support the following + * functions: + * <ul> + * <li>Parse and process the challenge sent by the targer server + * in response to request for a protected resource + * <li>Provide its textual designation + * <li>Provide its parameters, if available + * <li>Provide the realm this authentication scheme is applicable to, + * if available + * <li>Generate authorization string for the given set of credentials, + * request method and URI as specificed in the HTTP request line + * in response to the actual authorization challenge + * </ul> + * </p> + * <p> + * Authentication schemes may ignore method name and URI parameters + * if they are not relevant for the given authentication mechanism + * </p> + * <p> + * Authentication schemes may be stateful involving a series of + * challenge-response exchanges + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> + * + * @since 4.0 + */ + +public interface AuthScheme { + + /** + * Processes the given challenge token. Some authentication schemes + * may involve multiple challenge-response exchanges. Such schemes must be able + * to maintain the state information when dealing with sequential challenges + * + * @param header the challenge header + */ + void processChallenge(final Header header) throws MalformedChallengeException; + + /** + * Returns textual designation of the given authentication scheme. + * + * @return the name of the given authentication scheme + */ + String getSchemeName(); + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return the parameter with the given name + */ + String getParameter(final String name); + + /** + * Returns authentication realm. If the concept of an authentication + * realm is not applicable to the given authentication scheme, returns + * <code>null</code>. + * + * @return the authentication realm + */ + String getRealm(); + + /** + * Tests if the authentication scheme is provides authorization on a per + * connection basis instead of usual per request basis + * + * @return <tt>true</tt> if the scheme is connection based, <tt>false</tt> + * if the scheme is request based. + */ + boolean isConnectionBased(); + + /** + * Authentication process may involve a series of challenge-response exchanges. + * This method tests if the authorization process has been completed, either + * successfully or unsuccessfully, that is, all the required authorization + * challenges have been processed in their entirety. + * + * @return <tt>true</tt> if the authentication process has been completed, + * <tt>false</tt> otherwise. + */ + boolean isComplete(); + + /** + * Produces an authorization string for the given set of {@link Credentials}. + * + * @param credentials The set of credentials to be used for athentication + * @param request The request being authenticated + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return the authorization string + */ + Header authenticate(Credentials credentials, HttpRequest request) + throws AuthenticationException; + +} diff --git a/src/org/apache/http/auth/AuthSchemeFactory.java b/src/org/apache/http/auth/AuthSchemeFactory.java new file mode 100644 index 0000000..8f985b0 --- /dev/null +++ b/src/org/apache/http/auth/AuthSchemeFactory.java @@ -0,0 +1,46 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/AuthSchemeFactory.java $ + * $Revision: 527900 $ + * $Date: 2007-04-12 05:35:25 -0700 (Thu, 12 Apr 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface AuthSchemeFactory { + + AuthScheme newInstance(HttpParams params); + +} diff --git a/src/org/apache/http/auth/AuthSchemeRegistry.java b/src/org/apache/http/auth/AuthSchemeRegistry.java new file mode 100644 index 0000000..62a5d4d --- /dev/null +++ b/src/org/apache/http/auth/AuthSchemeRegistry.java @@ -0,0 +1,149 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/AuthSchemeRegistry.java $ + * $Revision: 652950 $ + * $Date: 2008-05-02 16:49:48 -0700 (Fri, 02 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.params.HttpParams; + +/** + * Authentication scheme registry that can be used to obtain the corresponding + * authentication scheme implementation for a given type of authorization challenge. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * @version $Revision: 652950 $ + * @since 4.0 + */ +public final class AuthSchemeRegistry { + + private final Map<String,AuthSchemeFactory> registeredSchemes; + + public AuthSchemeRegistry() { + super(); + this.registeredSchemes = new LinkedHashMap<String,AuthSchemeFactory>(); + } + + /** + * Registers a {@link AuthSchemeFactory} with the given identifier. If a factory with the + * given name already exists it will be overridden. This name is the same one used to + * retrieve the {@link AuthScheme authentication scheme} from {@link #getAuthScheme}. + * + * <p> + * Please note that custom authentication preferences, if used, need to be updated accordingly + * for the new {@link AuthScheme authentication scheme} to take effect. + * </p> + * + * @param name the identifier for this scheme + * @param factory the {@link AuthSchemeFactory} class to register + * + * @see #getAuthScheme + */ + public synchronized void register( + final String name, + final AuthSchemeFactory factory) { + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + if (factory == null) { + throw new IllegalArgumentException("Authentication scheme factory may not be null"); + } + registeredSchemes.put(name.toLowerCase(Locale.ENGLISH), factory); + } + + /** + * Unregisters the class implementing an {@link AuthScheme authentication scheme} with + * the given name. + * + * @param name the identifier of the class to unregister + */ + public synchronized void unregister(final String name) { + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + registeredSchemes.remove(name.toLowerCase(Locale.ENGLISH)); + } + + /** + * Gets the {@link AuthScheme authentication scheme} with the given name. + * + * @param name the {@link AuthScheme authentication scheme} identifier + * @param params the {@link HttpParams HTTP parameters} for the authentication + * scheme. + * + * @return {@link AuthScheme authentication scheme} + * + * @throws IllegalStateException if a scheme with the given name cannot be found + */ + public synchronized AuthScheme getAuthScheme(final String name, final HttpParams params) + throws IllegalStateException { + + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + AuthSchemeFactory factory = registeredSchemes.get(name.toLowerCase(Locale.ENGLISH)); + if (factory != null) { + return factory.newInstance(params); + } else { + throw new IllegalStateException("Unsupported authentication scheme: " + name); + } + } + + /** + * Obtains a list containing names of all registered {@link AuthScheme authentication + * schemes} in their default order. + * + * @return list of registered scheme names + */ + public synchronized List<String> getSchemeNames() { + return new ArrayList<String>(registeredSchemes.keySet()); + } + + /** + * Populates the internal collection of registered {@link AuthScheme authentication schemes} + * with the content of the map passed as a parameter. + * + * @param map authentication schemes + */ + public synchronized void setItems(final Map<String, AuthSchemeFactory> map) { + if (map == null) { + return; + } + registeredSchemes.clear(); + registeredSchemes.putAll(map); + } + +} diff --git a/src/org/apache/http/auth/AuthScope.java b/src/org/apache/http/auth/AuthScope.java new file mode 100644 index 0000000..c9d7f56 --- /dev/null +++ b/src/org/apache/http/auth/AuthScope.java @@ -0,0 +1,292 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/AuthScope.java $ + * $Revision: 652950 $ + * $Date: 2008-05-02 16:49:48 -0700 (Fri, 02 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import java.util.Locale; + +import org.apache.http.util.LangUtils; + +/** + * The class represents an authentication scope consisting of a host name, + * a port number, a realm name and an authentication scheme name which + * {@link Credentials Credentials} apply to. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:adrian@intencha.com">Adrian Sutton</a> + * + * @since 4.0 + */ +public class AuthScope { + + /** + * The <tt>null</tt> value represents any host. In the future versions of + * HttpClient the use of this parameter will be discontinued. + */ + public static final String ANY_HOST = null; + + /** + * The <tt>-1</tt> value represents any port. + */ + public static final int ANY_PORT = -1; + + /** + * The <tt>null</tt> value represents any realm. + */ + public static final String ANY_REALM = null; + + /** + * The <tt>null</tt> value represents any authentication scheme. + */ + public static final String ANY_SCHEME = null; + + /** + * Default scope matching any host, port, realm and authentication scheme. + * In the future versions of HttpClient the use of this parameter will be + * discontinued. + */ + public static final AuthScope ANY = new AuthScope(ANY_HOST, ANY_PORT, ANY_REALM, ANY_SCHEME); + + /** The authentication scheme the credentials apply to. */ + private final String scheme; + + /** The realm the credentials apply to. */ + private final String realm; + + /** The host the credentials apply to. */ + private final String host; + + /** The port the credentials apply to. */ + private final int port; + + /** Creates a new credentials scope for the given + * <tt>host</tt>, <tt>port</tt>, <tt>realm</tt>, and + * <tt>authentication scheme</tt>. + * + * @param host the host the credentials apply to. May be set + * to <tt>null</tt> if credenticals are applicable to + * any host. + * @param port the port the credentials apply to. May be set + * to negative value if credenticals are applicable to + * any port. + * @param realm the realm the credentials apply to. May be set + * to <tt>null</tt> if credenticals are applicable to + * any realm. + * @param scheme the authentication scheme the credentials apply to. + * May be set to <tt>null</tt> if credenticals are applicable to + * any authentication scheme. + */ + public AuthScope(final String host, int port, + final String realm, final String scheme) + { + this.host = (host == null) ? ANY_HOST: host.toLowerCase(Locale.ENGLISH); + this.port = (port < 0) ? ANY_PORT: port; + this.realm = (realm == null) ? ANY_REALM: realm; + this.scheme = (scheme == null) ? ANY_SCHEME: scheme.toUpperCase(Locale.ENGLISH); + } + + /** Creates a new credentials scope for the given + * <tt>host</tt>, <tt>port</tt>, <tt>realm</tt>, and any + * authentication scheme. + * + * @param host the host the credentials apply to. May be set + * to <tt>null</tt> if credenticals are applicable to + * any host. + * @param port the port the credentials apply to. May be set + * to negative value if credenticals are applicable to + * any port. + * @param realm the realm the credentials apply to. May be set + * to <tt>null</tt> if credenticals are applicable to + * any realm. + */ + public AuthScope(final String host, int port, final String realm) { + this(host, port, realm, ANY_SCHEME); + } + + /** Creates a new credentials scope for the given + * <tt>host</tt>, <tt>port</tt>, any realm name, and any + * authentication scheme. + * + * @param host the host the credentials apply to. May be set + * to <tt>null</tt> if credenticals are applicable to + * any host. + * @param port the port the credentials apply to. May be set + * to negative value if credenticals are applicable to + * any port. + */ + public AuthScope(final String host, int port) { + this(host, port, ANY_REALM, ANY_SCHEME); + } + + /** + * Creates a copy of the given credentials scope. + */ + public AuthScope(final AuthScope authscope) { + super(); + if (authscope == null) { + throw new IllegalArgumentException("Scope may not be null"); + } + this.host = authscope.getHost(); + this.port = authscope.getPort(); + this.realm = authscope.getRealm(); + this.scheme = authscope.getScheme(); + } + + /** + * @return the host + */ + public String getHost() { + return this.host; + } + + /** + * @return the port + */ + public int getPort() { + return this.port; + } + + /** + * @return the realm name + */ + public String getRealm() { + return this.realm; + } + + /** + * @return the scheme type + */ + public String getScheme() { + return this.scheme; + } + + /** + * Tests if the authentication scopes match. + * + * @return the match factor. Negative value signifies no match. + * Non-negative signifies a match. The greater the returned value + * the closer the match. + */ + public int match(final AuthScope that) { + int factor = 0; + if (LangUtils.equals(this.scheme, that.scheme)) { + factor += 1; + } else { + if (this.scheme != ANY_SCHEME && that.scheme != ANY_SCHEME) { + return -1; + } + } + if (LangUtils.equals(this.realm, that.realm)) { + factor += 2; + } else { + if (this.realm != ANY_REALM && that.realm != ANY_REALM) { + return -1; + } + } + if (this.port == that.port) { + factor += 4; + } else { + if (this.port != ANY_PORT && that.port != ANY_PORT) { + return -1; + } + } + if (LangUtils.equals(this.host, that.host)) { + factor += 8; + } else { + if (this.host != ANY_HOST && that.host != ANY_HOST) { + return -1; + } + } + return factor; + } + + /** + * @see java.lang.Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o == this) { + return true; + } + if (!(o instanceof AuthScope)) { + return super.equals(o); + } + AuthScope that = (AuthScope) o; + return + LangUtils.equals(this.host, that.host) + && this.port == that.port + && LangUtils.equals(this.realm, that.realm) + && LangUtils.equals(this.scheme, that.scheme); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + if (this.scheme != null) { + buffer.append(this.scheme.toUpperCase(Locale.ENGLISH)); + buffer.append(' '); + } + if (this.realm != null) { + buffer.append('\''); + buffer.append(this.realm); + buffer.append('\''); + } else { + buffer.append("<any realm>"); + } + if (this.host != null) { + buffer.append('@'); + buffer.append(this.host); + if (this.port >= 0) { + buffer.append(':'); + buffer.append(this.port); + } + } + return buffer.toString(); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.host); + hash = LangUtils.hashCode(hash, this.port); + hash = LangUtils.hashCode(hash, this.realm); + hash = LangUtils.hashCode(hash, this.scheme); + return hash; + } +} diff --git a/src/org/apache/http/auth/AuthState.java b/src/org/apache/http/auth/AuthState.java new file mode 100644 index 0000000..f55bf86 --- /dev/null +++ b/src/org/apache/http/auth/AuthState.java @@ -0,0 +1,147 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/AuthState.java $ + * $Revision: 659971 $ + * $Date: 2008-05-25 05:01:22 -0700 (Sun, 25 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + + +/** + * This class provides detailed information about the state of the + * authentication process. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class AuthState { + + /** Actual authentication scheme */ + private AuthScheme authScheme; + + /** Actual authentication scope */ + private AuthScope authScope; + + /** Credentials selected for authentication */ + private Credentials credentials; + + /** + * Default constructor. + * + */ + public AuthState() { + super(); + } + + /** + * Invalidates the authentication state by resetting its parameters. + */ + public void invalidate() { + this.authScheme = null; + this.authScope = null; + this.credentials = null; + } + + public boolean isValid() { + return this.authScheme != null; + } + + /** + * Assigns the given {@link AuthScheme authentication scheme}. + * + * @param authScheme the {@link AuthScheme authentication scheme} + */ + public void setAuthScheme(final AuthScheme authScheme) { + if (authScheme == null) { + invalidate(); + return; + } + this.authScheme = authScheme; + } + + /** + * Returns the {@link AuthScheme authentication scheme}. + * + * @return {@link AuthScheme authentication scheme} + */ + public AuthScheme getAuthScheme() { + return this.authScheme; + } + + + /** + * Returns user {@link Credentials} selected for authentication if available + * + * @return user credentials if available, <code>null</code otherwise + */ + public Credentials getCredentials() { + return this.credentials; + } + + + /** + * Sets user {@link Credentials} to be used for authentication + * + * @param credentials User credentials + */ + public void setCredentials(final Credentials credentials) { + this.credentials = credentials; + } + + + /** + * Returns actual {@link AuthScope} if available + * + * @return actual authentication scope if available, <code>null</code otherwise + */ + public AuthScope getAuthScope() { + return this.authScope; + } + + /** + * Sets actual {@link AuthScope}. + * + * @param authScope Authentication scope + */ + public void setAuthScope(final AuthScope authScope) { + this.authScope = authScope; + } + + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("auth scope ["); + buffer.append(this.authScope); + buffer.append("]; credentials set ["); + buffer.append(this.credentials != null ? "true" : "false"); + buffer.append("]"); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/auth/AuthenticationException.java b/src/org/apache/http/auth/AuthenticationException.java new file mode 100644 index 0000000..8b307be --- /dev/null +++ b/src/org/apache/http/auth/AuthenticationException.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/AuthenticationException.java $ + * $Revision: 505684 $ + * $Date: 2007-02-10 04:40:02 -0800 (Sat, 10 Feb 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import org.apache.http.ProtocolException; + +/** + * Signals a failure in authentication process + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class AuthenticationException extends ProtocolException { + + private static final long serialVersionUID = -6794031905674764776L; + + /** + * Creates a new AuthenticationException with a <tt>null</tt> detail message. + */ + public AuthenticationException() { + super(); + } + + /** + * Creates a new AuthenticationException with the specified message. + * + * @param message the exception detail message + */ + public AuthenticationException(String message) { + super(message); + } + + /** + * Creates a new AuthenticationException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public AuthenticationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/org/apache/http/auth/BasicUserPrincipal.java b/src/org/apache/http/auth/BasicUserPrincipal.java new file mode 100644 index 0000000..2485011 --- /dev/null +++ b/src/org/apache/http/auth/BasicUserPrincipal.java @@ -0,0 +1,90 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/BasicUserPrincipal.java $ + * $Revision: 658430 $ + * $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import java.security.Principal; + +import org.apache.http.util.LangUtils; + +/** + * Basic user principal used for HTTP authentication + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public final class BasicUserPrincipal implements Principal { + + private final String username; + + public BasicUserPrincipal(final String username) { + super(); + if (username == null) { + throw new IllegalArgumentException("User name may not be null"); + } + this.username = username; + } + + public String getName() { + return this.username; + } + + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.username); + return hash; + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (o instanceof BasicUserPrincipal) { + BasicUserPrincipal that = (BasicUserPrincipal) o; + if (LangUtils.equals(this.username, that.username)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("[principal: "); + buffer.append(this.username); + buffer.append("]"); + return buffer.toString(); + } + +} + diff --git a/src/org/apache/http/auth/Credentials.java b/src/org/apache/http/auth/Credentials.java new file mode 100644 index 0000000..846a23b --- /dev/null +++ b/src/org/apache/http/auth/Credentials.java @@ -0,0 +1,49 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/Credentials.java $ + * $Revision: 658430 $ + * $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import java.security.Principal; + +/** + * User name and password based authentication credentials. + * + * @author Unascribed + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @version $Revision: 658430 $ $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + */ +public interface Credentials { + + Principal getUserPrincipal(); + + String getPassword(); + +} diff --git a/src/org/apache/http/auth/InvalidCredentialsException.java b/src/org/apache/http/auth/InvalidCredentialsException.java new file mode 100644 index 0000000..50155ec --- /dev/null +++ b/src/org/apache/http/auth/InvalidCredentialsException.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/InvalidCredentialsException.java $ + * $Revision: 505684 $ + * $Date: 2007-02-10 04:40:02 -0800 (Sat, 10 Feb 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +/** + * Authentication credentials required to respond to a authentication + * challenge are invalid + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class InvalidCredentialsException extends AuthenticationException { + + private static final long serialVersionUID = -4834003835215460648L; + + /** + * Creates a new InvalidCredentialsException with a <tt>null</tt> detail message. + */ + public InvalidCredentialsException() { + super(); + } + + /** + * Creates a new InvalidCredentialsException with the specified message. + * + * @param message the exception detail message + */ + public InvalidCredentialsException(String message) { + super(message); + } + + /** + * Creates a new InvalidCredentialsException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public InvalidCredentialsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/auth/MalformedChallengeException.java b/src/org/apache/http/auth/MalformedChallengeException.java new file mode 100644 index 0000000..8c7e373 --- /dev/null +++ b/src/org/apache/http/auth/MalformedChallengeException.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/MalformedChallengeException.java $ + * $Revision: 505684 $ + * $Date: 2007-02-10 04:40:02 -0800 (Sat, 10 Feb 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import org.apache.http.ProtocolException; + +/** + * Signals that authentication challenge is in some way invalid or + * illegal in the given context + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class MalformedChallengeException extends ProtocolException { + + private static final long serialVersionUID = 814586927989932284L; + + /** + * Creates a new MalformedChallengeException with a <tt>null</tt> detail message. + */ + public MalformedChallengeException() { + super(); + } + + /** + * Creates a new MalformedChallengeException with the specified message. + * + * @param message the exception detail message + */ + public MalformedChallengeException(String message) { + super(message); + } + + /** + * Creates a new MalformedChallengeException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public MalformedChallengeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/auth/NTCredentials.java b/src/org/apache/http/auth/NTCredentials.java new file mode 100644 index 0000000..6800c42 --- /dev/null +++ b/src/org/apache/http/auth/NTCredentials.java @@ -0,0 +1,180 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/NTCredentials.java $ + * $Revision: 658430 $ + * $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import java.security.Principal; +import java.util.Locale; + +import org.apache.http.util.LangUtils; + +/** {@link Credentials} specific to the Windows platform. + * + * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 2.0 + */ +public class NTCredentials implements Credentials { + + /** The user principal */ + private final NTUserPrincipal principal; + + /** Password */ + private final String password; + + /** The host the authentication request is originating from. */ + private final String workstation; + + /** + * The constructor with the fully qualified username and password combined + * string argument. + * + * @param usernamePassword the domain/username:password formed string + */ + public NTCredentials(String usernamePassword) { + super(); + if (usernamePassword == null) { + throw new IllegalArgumentException("Username:password string may not be null"); + } + String username; + int atColon = usernamePassword.indexOf(':'); + if (atColon >= 0) { + username = usernamePassword.substring(0, atColon); + this.password = usernamePassword.substring(atColon + 1); + } else { + username = usernamePassword; + this.password = null; + } + int atSlash = username.indexOf('/'); + if (atSlash >= 0) { + this.principal = new NTUserPrincipal( + username.substring(0, atSlash).toUpperCase(Locale.ENGLISH), + username.substring(atSlash + 1)); + } else { + this.principal = new NTUserPrincipal( + null, + username.substring(atSlash + 1)); + } + this.workstation = null; + } + + /** + * Constructor. + * @param userName The user name. This should not include the domain to authenticate with. + * For example: "user" is correct whereas "DOMAIN\\user" is not. + * @param password The password. + * @param workstation The workstation the authentication request is originating from. + * Essentially, the computer name for this machine. + * @param domain The domain to authenticate within. + */ + public NTCredentials( + final String userName, + final String password, + final String workstation, + final String domain) { + super(); + if (userName == null) { + throw new IllegalArgumentException("User name may not be null"); + } + this.principal = new NTUserPrincipal(domain, userName); + this.password = password; + if (workstation != null) { + this.workstation = workstation.toUpperCase(Locale.ENGLISH); + } else { + this.workstation = null; + } + } + + public Principal getUserPrincipal() { + return this.principal; + } + + public String getUserName() { + return this.principal.getUsername(); + } + + public String getPassword() { + return this.password; + } + + /** + * Retrieves the name to authenticate with. + * + * @return String the domain these credentials are intended to authenticate with. + */ + public String getDomain() { + return this.principal.getDomain(); + } + + /** + * Retrieves the workstation name of the computer originating the request. + * + * @return String the workstation the user is logged into. + */ + public String getWorkstation() { + return this.workstation; + } + + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.principal); + hash = LangUtils.hashCode(hash, this.workstation); + return hash; + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (o instanceof NTCredentials) { + NTCredentials that = (NTCredentials) o; + if (LangUtils.equals(this.principal, that.principal) + && LangUtils.equals(this.workstation, that.workstation)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("[principal: "); + buffer.append(this.principal); + buffer.append("][workstation: "); + buffer.append(this.workstation); + buffer.append("]"); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/auth/NTUserPrincipal.java b/src/org/apache/http/auth/NTUserPrincipal.java new file mode 100644 index 0000000..ac91bb8 --- /dev/null +++ b/src/org/apache/http/auth/NTUserPrincipal.java @@ -0,0 +1,113 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/NTUserPrincipal.java $ + * $Revision: 658430 $ + * $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import java.security.Principal; +import java.util.Locale; + +import org.apache.http.util.LangUtils; + +/** NT (MS Windows specific) user principal used for HTTP authentication + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class NTUserPrincipal implements Principal { + + private final String username; + private final String domain; + private final String ntname; + + public NTUserPrincipal( + final String domain, + final String username) { + super(); + if (username == null) { + throw new IllegalArgumentException("User name may not be null"); + } + this.username = username; + if (domain != null) { + this.domain = domain.toUpperCase(Locale.ENGLISH); + } else { + this.domain = null; + } + if (this.domain != null && this.domain.length() > 0) { + StringBuilder buffer = new StringBuilder(); + buffer.append(this.domain); + buffer.append('/'); + buffer.append(this.username); + this.ntname = buffer.toString(); + } else { + this.ntname = this.username; + } + } + + public String getName() { + return this.ntname; + } + + public String getDomain() { + return this.domain; + } + + public String getUsername() { + return this.username; + } + + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.username); + hash = LangUtils.hashCode(hash, this.domain); + return hash; + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (o instanceof NTUserPrincipal) { + NTUserPrincipal that = (NTUserPrincipal) o; + if (LangUtils.equals(this.username, that.username) + && LangUtils.equals(this.domain, that.domain)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return this.ntname; + } + +} diff --git a/src/org/apache/http/auth/UsernamePasswordCredentials.java b/src/org/apache/http/auth/UsernamePasswordCredentials.java new file mode 100644 index 0000000..f82608c --- /dev/null +++ b/src/org/apache/http/auth/UsernamePasswordCredentials.java @@ -0,0 +1,126 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/UsernamePasswordCredentials.java $ + * $Revision: 658430 $ + * $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth; + +import java.security.Principal; + +import org.apache.http.util.LangUtils; + +/** + * Username and password {@link Credentials} + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Sean C. Sullivan + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 658430 $ $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + */ +public class UsernamePasswordCredentials implements Credentials { + + private final BasicUserPrincipal principal; + private final String password; + + /** + * The constructor with the username and password combined string argument. + * + * @param usernamePassword the username:password formed string + * @see #toString + */ + public UsernamePasswordCredentials(String usernamePassword) { + super(); + if (usernamePassword == null) { + throw new IllegalArgumentException("Username:password string may not be null"); + } + int atColon = usernamePassword.indexOf(':'); + if (atColon >= 0) { + this.principal = new BasicUserPrincipal(usernamePassword.substring(0, atColon)); + this.password = usernamePassword.substring(atColon + 1); + } else { + this.principal = new BasicUserPrincipal(usernamePassword); + this.password = null; + } + } + + + /** + * The constructor with the username and password arguments. + * + * @param userName the user name + * @param password the password + */ + public UsernamePasswordCredentials(String userName, String password) { + super(); + if (userName == null) { + throw new IllegalArgumentException("Username may not be null"); + } + this.principal = new BasicUserPrincipal(userName); + this.password = password; + } + + public Principal getUserPrincipal() { + return this.principal; + } + + public String getUserName() { + return this.principal.getName(); + } + + public String getPassword() { + return password; + } + + @Override + public int hashCode() { + return this.principal.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (o instanceof UsernamePasswordCredentials) { + UsernamePasswordCredentials that = (UsernamePasswordCredentials) o; + if (LangUtils.equals(this.principal, that.principal)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return this.principal.toString(); + } + +} + diff --git a/src/org/apache/http/auth/package.html b/src/org/apache/http/auth/package.html new file mode 100644 index 0000000..1493dfb --- /dev/null +++ b/src/org/apache/http/auth/package.html @@ -0,0 +1,41 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The API for client-side HTTP authentication against a server, +commonly referred to as <i>HttpAuth</i>. + +</body> +</html> diff --git a/src/org/apache/http/auth/params/AuthPNames.java b/src/org/apache/http/auth/params/AuthPNames.java new file mode 100644 index 0000000..a053435 --- /dev/null +++ b/src/org/apache/http/auth/params/AuthPNames.java @@ -0,0 +1,56 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/params/AuthPNames.java $ + * $Revision: 578403 $ + * $Date: 2007-09-22 03:56:04 -0700 (Sat, 22 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth.params; + +/** + * Parameter names for HttpAuth. + * + * @version $Revision: 578403 $ + * + * @since 4.0 + */ +public interface AuthPNames { + + /** + * Defines the charset to be used when encoding + * {@link org.apache.http.auth.Credentials}. + * <p> + * This parameter expects a value of type {@link String}. + * If not defined, then + * {@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET + * HttpProtocolParams.HTTP_ELEMENT_CHARSET} + * should be used. + * </p> + */ + public static final String CREDENTIAL_CHARSET = "http.auth.credential-charset"; + +} diff --git a/src/org/apache/http/auth/params/AuthParamBean.java b/src/org/apache/http/auth/params/AuthParamBean.java new file mode 100644 index 0000000..5b27328 --- /dev/null +++ b/src/org/apache/http/auth/params/AuthParamBean.java @@ -0,0 +1,47 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/params/AuthParamBean.java $ + * $Revision: 632313 $ + * $Date: 2008-02-29 05:19:50 -0800 (Fri, 29 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth.params; + +import org.apache.http.params.HttpAbstractParamBean; +import org.apache.http.params.HttpParams; + +public class AuthParamBean extends HttpAbstractParamBean { + + public AuthParamBean (final HttpParams params) { + super(params); + } + + public void setCredentialCharset (final String charset) { + AuthParams.setCredentialCharset(params, charset); + } + +} diff --git a/src/org/apache/http/auth/params/AuthParams.java b/src/org/apache/http/auth/params/AuthParams.java new file mode 100644 index 0000000..a724a8c --- /dev/null +++ b/src/org/apache/http/auth/params/AuthParams.java @@ -0,0 +1,94 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/params/AuthParams.java $ + * $Revision: 618365 $ + * $Date: 2008-02-04 10:20:08 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.auth.params; + +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HTTP; + +/** + * This class implements an adaptor around the {@link HttpParams} interface + * to simplify manipulation of the HTTP authentication specific parameters. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618365 $ + * + * @since 4.0 + * + * @see AuthPNames + */ +public final class AuthParams { + + private AuthParams() { + super(); + } + + /** + * Obtains the charset for encoding + * {@link org.apache.http.auth.Credentials}. + * If not configured, + * {@link HTTP#DEFAULT_PROTOCOL_CHARSET HTTP.DEFAULT_PROTOCOL_CHARSET} + * is used instead. + * + * @return The charset + * + * @see AuthPNames#CREDENTIAL_CHARSET + */ + public static String getCredentialCharset(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + String charset = (String) params.getParameter + (AuthPNames.CREDENTIAL_CHARSET); + //@@@ TODO: inconsistent with JavaDoc in AuthPNames, + //@@@ TODO: check HTTP_ELEMENT_CHARSET first, or fix JavaDocs + if (charset == null) { + charset = HTTP.DEFAULT_PROTOCOL_CHARSET; + } + return charset; + } + + + /** + * Sets the charset to be used when encoding + * {@link org.apache.http.auth.Credentials}. + * + * @param charset The charset + */ + public static void setCredentialCharset(final HttpParams params, final String charset) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setParameter(AuthPNames.CREDENTIAL_CHARSET, charset); + } + +} diff --git a/src/org/apache/http/auth/params/package.html b/src/org/apache/http/auth/params/package.html new file mode 100644 index 0000000..f9258fd --- /dev/null +++ b/src/org/apache/http/auth/params/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/auth/params/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Parameters for configuring <i>HttpAuth</i>. + +</body> +</html> diff --git a/src/org/apache/http/client/AuthenticationHandler.java b/src/org/apache/http/client/AuthenticationHandler.java new file mode 100644 index 0000000..dacc1b8 --- /dev/null +++ b/src/org/apache/http/client/AuthenticationHandler.java @@ -0,0 +1,61 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/AuthenticationHandler.java $ + * $Revision: 603318 $ + * $Date: 2007-12-11 10:06:50 -0800 (Tue, 11 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public interface AuthenticationHandler { + + boolean isAuthenticationRequested( + HttpResponse response, + HttpContext context); + + Map<String, Header> getChallenges( + HttpResponse response, + HttpContext context) throws MalformedChallengeException; + + AuthScheme selectScheme( + Map<String, Header> challenges, + HttpResponse response, + HttpContext context) throws AuthenticationException; + +} diff --git a/src/org/apache/http/client/CircularRedirectException.java b/src/org/apache/http/client/CircularRedirectException.java new file mode 100644 index 0000000..08dca63 --- /dev/null +++ b/src/org/apache/http/client/CircularRedirectException.java @@ -0,0 +1,70 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/CircularRedirectException.java $ + * $Revision: 558123 $ + * $Date: 2007-07-20 13:29:58 -0700 (Fri, 20 Jul 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +/** + * Signals a circular redirect + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class CircularRedirectException extends RedirectException { + + private static final long serialVersionUID = 6830063487001091803L; + + /** + * Creates a new CircularRedirectException with a <tt>null</tt> detail message. + */ + public CircularRedirectException() { + super(); + } + + /** + * Creates a new CircularRedirectException with the specified detail message. + * + * @param message The exception detail message + */ + public CircularRedirectException(String message) { + super(message); + } + + /** + * Creates a new CircularRedirectException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public CircularRedirectException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/client/ClientProtocolException.java b/src/org/apache/http/client/ClientProtocolException.java new file mode 100644 index 0000000..b5a991a --- /dev/null +++ b/src/org/apache/http/client/ClientProtocolException.java @@ -0,0 +1,60 @@ +/* + * $HeadURL: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.io.IOException; + +/** + * Signals an error in the HTTP protocol. + */ +public class ClientProtocolException extends IOException { + + private static final long serialVersionUID = -5596590843227115865L; + + public ClientProtocolException() { + super(); + } + + public ClientProtocolException(String s) { + super(s); + } + + public ClientProtocolException(Throwable cause) { + initCause(cause); + } + + public ClientProtocolException(String message, Throwable cause) { + super(message); + initCause(cause); + } + + +} diff --git a/src/org/apache/http/client/CookieStore.java b/src/org/apache/http/client/CookieStore.java new file mode 100644 index 0000000..bc239ac --- /dev/null +++ b/src/org/apache/http/client/CookieStore.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/CookieStore.java $ + * $Revision: 604434 $ + * $Date: 2007-12-15 06:45:48 -0800 (Sat, 15 Dec 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.util.Date; +import java.util.List; + +import org.apache.http.cookie.Cookie; + +/** + * Abstract cookie store. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface CookieStore { + + /** + * Adds an {@link Cookie HTTP cookie}, replacing any existing equivalent cookies. + * If the given cookie has already expired it will not be added, but existing + * values will still be removed. + * + * @param cookie the {@link Cookie cookie} to be added + */ + void addCookie(Cookie cookie); + + /** + * Returns all cookies contained in this store. + * + * @return all cookies + */ + List<Cookie> getCookies(); + + /** + * Removes all of {@link Cookie cookies} in this store that have expired by + * the specified {@link java.util.Date date}. + * + * @return true if any cookies were purged. + */ + boolean clearExpired(Date date); + + /** + * Clears all cookies. + */ + void clear(); + +} diff --git a/src/org/apache/http/client/CredentialsProvider.java b/src/org/apache/http/client/CredentialsProvider.java new file mode 100644 index 0000000..8396d84 --- /dev/null +++ b/src/org/apache/http/client/CredentialsProvider.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/CredentialsProvider.java $ + * $Revision: 558124 $ + * $Date: 2007-07-20 13:36:47 -0700 (Fri, 20 Jul 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; + +/** + * Abstract credentials provider. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface CredentialsProvider { + + /** + * Sets the {@link Credentials credentials} for the given authentication + * scope. Any previous credentials for the given scope will be overwritten. + * + * @param authscope the {@link AuthScope authentication scope} + * @param credentials the authentication {@link Credentials credentials} + * for the given scope. + * + * @see #getCredentials(AuthScope) + */ + void setCredentials(AuthScope authscope, Credentials credentials); + + /** + * Get the {@link Credentials credentials} for the given authentication scope. + * + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + * @see #setCredentials(AuthScope, Credentials) + */ + Credentials getCredentials(AuthScope authscope); + + /** + * Clears all credentials. + */ + void clear(); + +} diff --git a/src/org/apache/http/client/HttpClient.java b/src/org/apache/http/client/HttpClient.java new file mode 100644 index 0000000..aaa09e0 --- /dev/null +++ b/src/org/apache/http/client/HttpClient.java @@ -0,0 +1,249 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpClient.java $ + * $Revision: 676020 $ + * $Date: 2008-07-11 09:38:49 -0700 (Fri, 11 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; + +/** + * Interface for an HTTP client. + * HTTP clients encapsulate a smorgasbord of objects required to + * execute HTTP requests while handling cookies, authentication, + * connection management, and other features. + * Thread safety of HTTP clients depends on the implementation + * and configuration of the specific client. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 676020 $ + * + * @since 4.0 + */ +public interface HttpClient { + + + /** + * Obtains the parameters for this client. + * These parameters will become defaults for all requests being + * executed with this client, and for the parameters of + * dependent objects in this client. + * + * @return the default parameters + */ + HttpParams getParams() + ; + + + /** + * Obtains the connection manager used by this client. + * + * @return the connection manager + */ + ClientConnectionManager getConnectionManager() + ; + + /** + * Executes a request using the default context. + * + * @param request the request to execute + * + * @return the response to the request. This is always a final response, + * never an intermediate response with an 1xx status code. + * Whether redirects or authentication challenges will be returned + * or handled automatically depends on the implementation and + * configuration of this client. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + HttpResponse execute(HttpUriRequest request) + throws IOException, ClientProtocolException + ; + + + /** + * Executes a request using the given context. + * The route to the target will be determined by the HTTP client. + * + * @param request the request to execute + * @param context the context to use for the execution, or + * <code>null</code> to use the default context + * + * @return the response to the request. This is always a final response, + * never an intermediate response with an 1xx status code. + * Whether redirects or authentication challenges will be returned + * or handled automatically depends on the implementation and + * configuration of this client. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + HttpResponse execute(HttpUriRequest request, HttpContext context) + throws IOException, ClientProtocolException + ; + + + /** + * Executes a request to the target using the default context. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * + * @return the response to the request. This is always a final response, + * never an intermediate response with an 1xx status code. + * Whether redirects or authentication challenges will be returned + * or handled automatically depends on the implementation and + * configuration of this client. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + HttpResponse execute(HttpHost target, HttpRequest request) + throws IOException, ClientProtocolException + ; + + /** + * Executes a request to the target using the given context. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context to use for the execution, or + * <code>null</code> to use the default context + * + * @return the response to the request. This is always a final response, + * never an intermediate response with an 1xx status code. + * Whether redirects or authentication challenges will be returned + * or handled automatically depends on the implementation and + * configuration of this client. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) + throws IOException, ClientProtocolException + ; + + /** + * Executes a request using the default context and processes the + * response using the given response handler. + * + * @param request the request to execute + * @param responseHandler the response handler + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + <T> T execute( + HttpUriRequest request, + ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException + ; + + /** + * Executes a request using the given context and processes the + * response using the given response handler. + * + * @param request the request to execute + * @param responseHandler the response handler + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + <T> T execute( + HttpUriRequest request, + ResponseHandler<? extends T> responseHandler, + HttpContext context) + throws IOException, ClientProtocolException + ; + + /** + * Executes a request to the target using the default context and + * processes the response using the given response handler. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + <T> T execute( + HttpHost target, + HttpRequest request, + ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException + ; + + /** + * Executes a request to the target using the given context and + * processes the response using the given response handler. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * @param context the context to use for the execution, or + * <code>null</code> to use the default context + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + <T> T execute( + HttpHost target, + HttpRequest request, + ResponseHandler<? extends T> responseHandler, + HttpContext context) + throws IOException, ClientProtocolException + ; + +} // interface HttpClient diff --git a/src/org/apache/http/client/HttpRequestRetryHandler.java b/src/org/apache/http/client/HttpRequestRetryHandler.java new file mode 100644 index 0000000..9ef8ef9 --- /dev/null +++ b/src/org/apache/http/client/HttpRequestRetryHandler.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpRequestRetryHandler.java $ + * $Revision: 535610 $ + * $Date: 2007-05-06 06:28:13 -0700 (Sun, 06 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.io.IOException; + +import org.apache.http.protocol.HttpContext; + +/** + * A handler for determining if an HttpRequest should be retried after a + * recoverable exception during execution. + * + * <p> + * Classes implementing this interface must synchronize access to shared + * data as methods of this interfrace may be executed from multiple threads + * </p> + * + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public interface HttpRequestRetryHandler { + + /** + * Determines if a method should be retried after an IOException + * occurs during execution. + * + * @param exception the exception that occurred + * @param executionCount the number of times this method has been + * unsuccessfully executed + * @param context the context for the request execution + * + * @return <code>true</code> if the method should be retried, <code>false</code> + * otherwise + */ + boolean retryRequest(IOException exception, int executionCount, HttpContext context); + +} diff --git a/src/org/apache/http/client/HttpResponseException.java b/src/org/apache/http/client/HttpResponseException.java new file mode 100644 index 0000000..4d8de91 --- /dev/null +++ b/src/org/apache/http/client/HttpResponseException.java @@ -0,0 +1,51 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpResponseException.java $ + * $Revision: 672425 $ + * $Date: 2008-06-27 16:33:05 -0700 (Fri, 27 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +/** + * Signals a non 2xx HTTP response. + */ +public class HttpResponseException extends ClientProtocolException { + + private static final long serialVersionUID = -7186627969477257933L; + + private final int statusCode; + + public HttpResponseException(int statusCode, final String s) { + super(s); + this.statusCode = statusCode; + } + + public int getStatusCode() { + return this.statusCode; + } + +} diff --git a/src/org/apache/http/client/NonRepeatableRequestException.java b/src/org/apache/http/client/NonRepeatableRequestException.java new file mode 100644 index 0000000..13ff4d1 --- /dev/null +++ b/src/org/apache/http/client/NonRepeatableRequestException.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/NonRepeatableRequestException.java $ + * $Revision: 664326 $ + * $Date: 2008-06-07 04:48:27 -0700 (Sat, 07 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import org.apache.http.ProtocolException; + +/** + * Signals failure to retry the request due to non-repeatable request + * entity. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class NonRepeatableRequestException extends ProtocolException { + + private static final long serialVersionUID = 82685265288806048L; + + /** + * Creates a new NonRepeatableEntityException with a <tt>null</tt> detail message. + */ + public NonRepeatableRequestException() { + super(); + } + + /** + * Creates a new NonRepeatableEntityException with the specified detail message. + * + * @param message The exception detail message + */ + public NonRepeatableRequestException(String message) { + super(message); + } + +} diff --git a/src/org/apache/http/client/RedirectException.java b/src/org/apache/http/client/RedirectException.java new file mode 100644 index 0000000..82ea9ea --- /dev/null +++ b/src/org/apache/http/client/RedirectException.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/RedirectException.java $ + * $Revision: 664066 $ + * $Date: 2008-06-06 11:13:18 -0700 (Fri, 06 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import org.apache.http.ProtocolException; + +/** + * Signals violation of HTTP specification caused by an invalid redirect + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class RedirectException extends ProtocolException { + + private static final long serialVersionUID = 4418824536372559326L; + + /** + * Creates a new RedirectException with a <tt>null</tt> detail message. + */ + public RedirectException() { + super(); + } + + /** + * Creates a new RedirectException with the specified detail message. + * + * @param message The exception detail message + */ + public RedirectException(String message) { + super(message); + } + + /** + * Creates a new RedirectException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public RedirectException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/client/RedirectHandler.java b/src/org/apache/http/client/RedirectHandler.java new file mode 100644 index 0000000..a98b4ae --- /dev/null +++ b/src/org/apache/http/client/RedirectHandler.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/RedirectHandler.java $ + * $Revision: 538647 $ + * $Date: 2007-05-16 09:41:42 -0700 (Wed, 16 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.net.URI; + +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolException; +import org.apache.http.protocol.HttpContext; + +/** + * A handler for determining if an HTTP request should be redirected to + * a new location in response to an HTTP response received from the target + * server. + * + * <p> + * Classes implementing this interface must synchronize access to shared + * data as methods of this interfrace may be executed from multiple threads + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public interface RedirectHandler { + + /** + * Determines if a request should be redirected to a new location + * given the response from the target server. + * + * @param response the response received from the target server + * @param context the context for the request execution + * + * @return <code>true</code> if the request should be redirected, <code>false</code> + * otherwise + */ + boolean isRedirectRequested(HttpResponse response, HttpContext context); + + /** + * Determines the location request is expected to be redirected to + * given the response from the target server and the current request + * execution context. + * + * @param response the response received from the target server + * @param context the context for the request execution + * + * @return redirect URI + */ + URI getLocationURI(HttpResponse response, HttpContext context) + throws ProtocolException; + +} diff --git a/src/org/apache/http/client/RequestDirector.java b/src/org/apache/http/client/RequestDirector.java new file mode 100644 index 0000000..924c312 --- /dev/null +++ b/src/org/apache/http/client/RequestDirector.java @@ -0,0 +1,92 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/RequestDirector.java $ + * $Revision: 676020 $ + * $Date: 2008-07-11 09:38:49 -0700 (Fri, 11 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpException; +import org.apache.http.protocol.HttpContext; + +/** + * A client-side request director. + * The director decides which steps are necessary to execute a request. + * It establishes connections and optionally processes redirects and + * authentication challenges. The director may therefore generate and + * send a sequence of requests in order to execute one initial request. + * + * <br/><b>Note:</b> + * It is most likely that implementations of this interface will + * allocate connections, and return responses that depend on those + * connections for reading the response entity. Such connections + * MUST be released, but that is out of the scope of a request director. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 676020 $ + * + * @since 4.0 + */ +public interface RequestDirector { + + + /** + * Executes a request. + * <br/><b>Note:</b> + * For the time being, a new director is instantiated for each request. + * This is the same behavior as for <code>HttpMethodDirector</code> + * in HttpClient 3. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context for executing the request + * + * @return the final response to the request. + * This is never an intermediate response with status code 1xx. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + * or if the connection was aborted + */ + HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) + throws HttpException, IOException + ; + +} // class ClientRequestDirector diff --git a/src/org/apache/http/client/ResponseHandler.java b/src/org/apache/http/client/ResponseHandler.java new file mode 100644 index 0000000..33a3391 --- /dev/null +++ b/src/org/apache/http/client/ResponseHandler.java @@ -0,0 +1,59 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/ResponseHandler.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import java.io.IOException; + +import org.apache.http.HttpResponse; + +/** + * Handler that encapsulates the process of generating a response object + * from a {@link HttpResponse}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface ResponseHandler<T> { + + /** + * Processes an {@link HttpResponse} and returns some value + * corresponding to that response. + * + * @param response The response to process + * @return A value determined by the response + * + * @throws ClientProtocolException in case of an http protocol error + * @throws IOException in case of a problem or the connection was aborted + */ + T handleResponse(HttpResponse response) throws ClientProtocolException, IOException; + +} diff --git a/src/org/apache/http/client/UserTokenHandler.java b/src/org/apache/http/client/UserTokenHandler.java new file mode 100644 index 0000000..f8e55d8 --- /dev/null +++ b/src/org/apache/http/client/UserTokenHandler.java @@ -0,0 +1,64 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/UserTokenHandler.java $ + * $Revision: 658759 $ + * $Date: 2008-05-21 10:06:17 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client; + +import org.apache.http.protocol.HttpContext; + +/** + * A handler for determining if the given execution context is user specific + * or not. The token object returned by this handler is expected to uniquely + * identify the current user if the context is user specific or to be + * <code>null</code> if the context does not contain any resources or details + * specific to the current user. + * <p/> + * The user token will be used to ensure that user specific resouces will not + * shared with or reused by other users. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface UserTokenHandler { + + /** + * The token object returned by this method is expected to uniquely + * identify the current user if the context is user specific or to be + * <code>null</code> if it is not. + * + * @param context the execution context + * + * @return user token that uniquely identifies the user or + * <code>null</null> if the context is not user specific. + */ + Object getUserToken(HttpContext context); + +} diff --git a/src/org/apache/http/client/entity/UrlEncodedFormEntity.java b/src/org/apache/http/client/entity/UrlEncodedFormEntity.java new file mode 100644 index 0000000..89b9c45 --- /dev/null +++ b/src/org/apache/http/client/entity/UrlEncodedFormEntity.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/entity/UrlEncodedFormEntity.java $ + * $Revision: 655107 $ + * $Date: 2008-05-10 08:20:42 -0700 (Sat, 10 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.entity; + +import java.io.UnsupportedEncodingException; +import java.util.List; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.entity.StringEntity; +import org.apache.http.protocol.HTTP; + +/** + * An entity composed of a list of url-encoded pairs. + * This is typically useful while sending an HTTP POST request. + */ +public class UrlEncodedFormEntity extends StringEntity { + + /** + * Constructs a new {@link UrlEncodedFormEntity} with the list + * of parameters in the specified encoding. + * + * @param parameters list of name/value pairs + * @param encoding encoding the name/value pairs be encoded with + * @throws UnsupportedEncodingException if the encoding isn't supported + */ + public UrlEncodedFormEntity ( + final List <? extends NameValuePair> parameters, + final String encoding) throws UnsupportedEncodingException { + super(URLEncodedUtils.format(parameters, encoding), + encoding); + setContentType(URLEncodedUtils.CONTENT_TYPE); + } + + /** + * Constructs a new {@link UrlEncodedFormEntity} with the list + * of parameters with the default encoding of {@link HTTP#DEFAULT_CONTENT_CHARSET} + * + * @param parameters list of name/value pairs + * @throws UnsupportedEncodingException if the default encoding isn't supported + */ + public UrlEncodedFormEntity ( + final List <? extends NameValuePair> parameters) throws UnsupportedEncodingException { + super(URLEncodedUtils.format(parameters, HTTP.DEFAULT_CONTENT_CHARSET), + HTTP.DEFAULT_CONTENT_CHARSET); + setContentType(URLEncodedUtils.CONTENT_TYPE); + } + +} diff --git a/src/org/apache/http/client/methods/AbortableHttpRequest.java b/src/org/apache/http/client/methods/AbortableHttpRequest.java new file mode 100644 index 0000000..c402609 --- /dev/null +++ b/src/org/apache/http/client/methods/AbortableHttpRequest.java @@ -0,0 +1,91 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java $ + * $Revision: 639600 $ + * $Date: 2008-03-21 04:28:15 -0700 (Fri, 21 Mar 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.io.IOException; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionReleaseTrigger; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; + +/** + * Interface representing an HTTP request that can be aborted by shutting + * down the underlying HTTP connection. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 639600 $ + * + * @since 4.0 + */ +public interface AbortableHttpRequest { + + /** + * Sets the {@link ClientConnectionRequest} callback that can be + * used to abort a long-lived request for a connection. + * If the request is already aborted, throws an {@link IOException}. + * + * @see ClientConnectionManager + * @see ThreadSafeClientConnManager + */ + void setConnectionRequest(ClientConnectionRequest connRequest) throws IOException; + + /** + * Sets the {@link ConnectionReleaseTrigger} callback that can + * be used to abort an active connection. + * Typically, this will be the {@link ManagedClientConnection} itself. + * If the request is already aborted, throws an {@link IOException}. + */ + void setReleaseTrigger(ConnectionReleaseTrigger releaseTrigger) throws IOException; + + /** + * Aborts this http request. Any active execution of this method should + * return immediately. If the request has not started, it will abort after + * the next execution. Aborting this request will cause all subsequent + * executions with this request to fail. + * + * @see HttpClient#execute(HttpUriRequest) + * @see HttpClient#execute(org.apache.http.HttpHost, + * org.apache.http.HttpRequest) + * @see HttpClient#execute(HttpUriRequest, + * org.apache.http.protocol.HttpContext) + * @see HttpClient#execute(org.apache.http.HttpHost, + * org.apache.http.HttpRequest, org.apache.http.protocol.HttpContext) + */ + void abort(); + +} + diff --git a/src/org/apache/http/client/methods/HttpDelete.java b/src/org/apache/http/client/methods/HttpDelete.java new file mode 100644 index 0000000..4a0fb77 --- /dev/null +++ b/src/org/apache/http/client/methods/HttpDelete.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpDelete.java $ + * $Revision: 664505 $ + * $Date: 2008-06-08 06:21:20 -0700 (Sun, 08 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; + +/** + * HTTP DELETE method + * <p> + * The HTTP DELETE method is defined in section 9.7 of + * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: + * <blockquote> + * The DELETE method requests that the origin server delete the resource + * identified by the Request-URI. [...] The client cannot + * be guaranteed that the operation has been carried out, even if the + * status code returned from the origin server indicates that the action + * has been completed successfully. + * </blockquote> + */ +public class HttpDelete extends HttpRequestBase { + + public final static String METHOD_NAME = "DELETE"; + + + public HttpDelete() { + super(); + } + + public HttpDelete(final URI uri) { + super(); + setURI(uri); + } + + /** + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpDelete(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + +} diff --git a/src/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java b/src/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java new file mode 100644 index 0000000..8ac6f01 --- /dev/null +++ b/src/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java @@ -0,0 +1,81 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.client.utils.CloneUtils; +import org.apache.http.protocol.HTTP; + +/** + * Basic implementation of an HTTP request that can be modified. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public abstract class HttpEntityEnclosingRequestBase + extends HttpRequestBase implements HttpEntityEnclosingRequest { + + private HttpEntity entity; + + public HttpEntityEnclosingRequestBase() { + super(); + } + + public HttpEntity getEntity() { + return this.entity; + } + + public void setEntity(final HttpEntity entity) { + this.entity = entity; + } + + public boolean expectContinue() { + Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); + return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + HttpEntityEnclosingRequestBase clone = + (HttpEntityEnclosingRequestBase) super.clone(); + if (this.entity != null) { + clone.entity = (HttpEntity) CloneUtils.clone(this.entity); + } + return clone; + } + +} diff --git a/src/org/apache/http/client/methods/HttpGet.java b/src/org/apache/http/client/methods/HttpGet.java new file mode 100644 index 0000000..2908f1d --- /dev/null +++ b/src/org/apache/http/client/methods/HttpGet.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpGet.java $ + * $Revision: 664505 $ + * $Date: 2008-06-08 06:21:20 -0700 (Sun, 08 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; + +/** + * HTTP GET method. + * <p> + * The HTTP GET method is defined in section 9.3 of + * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: + * <blockquote> + * The GET method means retrieve whatever information (in the form of an + * entity) is identified by the Request-URI. If the Request-URI refers + * to a data-producing process, it is the produced data which shall be + * returned as the entity in the response and not the source text of the + * process, unless that text happens to be the output of the process. + * </blockquote> + * </p> + * <p> + * GetMethods will follow redirect requests from the http server by default. + * This behavour can be disabled by calling setFollowRedirects(false).</p> + * + * @version $Revision: 664505 $ + * + * @since 4.0 + */ +public class HttpGet extends HttpRequestBase { + + public final static String METHOD_NAME = "GET"; + + public HttpGet() { + super(); + } + + public HttpGet(final URI uri) { + super(); + setURI(uri); + } + + /** + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpGet(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + +} diff --git a/src/org/apache/http/client/methods/HttpHead.java b/src/org/apache/http/client/methods/HttpHead.java new file mode 100644 index 0000000..29e58a3 --- /dev/null +++ b/src/org/apache/http/client/methods/HttpHead.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpHead.java $ + * $Revision: 664505 $ + * $Date: 2008-06-08 06:21:20 -0700 (Sun, 08 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; + +/** + * HTTP HEAD method. + * <p> + * The HTTP HEAD method is defined in section 9.4 of + * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: + * <blockquote> + * The HEAD method is identical to GET except that the server MUST NOT + * return a message-body in the response. The metainformation contained + * in the HTTP headers in response to a HEAD request SHOULD be identical + * to the information sent in response to a GET request. This method can + * be used for obtaining metainformation about the entity implied by the + * request without transferring the entity-body itself. This method is + * often used for testing hypertext links for validity, accessibility, + * and recent modification. + * </blockquote> + * </p> + * + * @version $Revision: 664505 $ + * + * @since 4.0 + */ +public class HttpHead extends HttpRequestBase { + + public final static String METHOD_NAME = "HEAD"; + + public HttpHead() { + super(); + } + + public HttpHead(final URI uri) { + super(); + setURI(uri); + } + + /** + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpHead(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + +} diff --git a/src/org/apache/http/client/methods/HttpOptions.java b/src/org/apache/http/client/methods/HttpOptions.java new file mode 100644 index 0000000..3758360 --- /dev/null +++ b/src/org/apache/http/client/methods/HttpOptions.java @@ -0,0 +1,105 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpOptions.java $ + * $Revision: 664505 $ + * $Date: 2008-06-08 06:21:20 -0700 (Sun, 08 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; +import java.util.HashSet; +import java.util.Set; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HeaderIterator; +import org.apache.http.HttpResponse; + +/** + * HTTP OPTIONS method. + * <p> + * The HTTP OPTIONS method is defined in section 9.2 of + * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: + * <blockquote> + * The OPTIONS method represents a request for information about the + * communication options available on the request/response chain + * identified by the Request-URI. This method allows the client to + * determine the options and/or requirements associated with a resource, + * or the capabilities of a server, without implying a resource action + * or initiating a resource retrieval. + * </blockquote> + * </p> + * + * @version $Revision: 664505 $ + * + * @since 4.0 + */ +public class HttpOptions extends HttpRequestBase { + + public final static String METHOD_NAME = "OPTIONS"; + + public HttpOptions() { + super(); + } + + public HttpOptions(final URI uri) { + super(); + setURI(uri); + } + + /** + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpOptions(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + + public Set<String> getAllowedMethods(final HttpResponse response) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + + HeaderIterator it = response.headerIterator("Allow"); + Set<String> methods = new HashSet<String>(); + while (it.hasNext()) { + Header header = it.nextHeader(); + HeaderElement[] elements = header.getElements(); + for (HeaderElement element : elements) { + methods.add(element.getName()); + } + } + return methods; + } + +} diff --git a/src/org/apache/http/client/methods/HttpPost.java b/src/org/apache/http/client/methods/HttpPost.java new file mode 100644 index 0000000..bc58803 --- /dev/null +++ b/src/org/apache/http/client/methods/HttpPost.java @@ -0,0 +1,87 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpPost.java $ + * $Revision: 664505 $ + * $Date: 2008-06-08 06:21:20 -0700 (Sun, 08 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; + +/** + * HTTP POST method. + * <p> + * The HTTP POST method is defined in section 9.5 of + * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: + * <blockquote> + * The POST method is used to request that the origin server accept the entity + * enclosed in the request as a new subordinate of the resource identified by + * the Request-URI in the Request-Line. POST is designed to allow a uniform + * method to cover the following functions: + * <ul> + * <li>Annotation of existing resources</li> + * <li>Posting a message to a bulletin board, newsgroup, mailing list, or + * similar group of articles</li> + * <li>Providing a block of data, such as the result of submitting a form, + * to a data-handling process</li> + * <li>Extending a database through an append operation</li> + * </ul> + * </blockquote> + * </p> + * + * @version $Revision: 664505 $ + * + * @since 4.0 + */ +public class HttpPost extends HttpEntityEnclosingRequestBase { + + public final static String METHOD_NAME = "POST"; + + public HttpPost() { + super(); + } + + public HttpPost(final URI uri) { + super(); + setURI(uri); + } + + /** + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpPost(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + +} diff --git a/src/org/apache/http/client/methods/HttpPut.java b/src/org/apache/http/client/methods/HttpPut.java new file mode 100644 index 0000000..5b50135 --- /dev/null +++ b/src/org/apache/http/client/methods/HttpPut.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpPut.java $ + * $Revision: 664505 $ + * $Date: 2008-06-08 06:21:20 -0700 (Sun, 08 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; + +/** + * HTTP PUT method. + * <p> + * The HTTP PUT method is defined in section 9.6 of + * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: + * <blockquote> + * The PUT method requests that the enclosed entity be stored under the + * supplied Request-URI. If the Request-URI refers to an already + * existing resource, the enclosed entity SHOULD be considered as a + * modified version of the one residing on the origin server. + * </blockquote> + * </p> + * + * @version $Revision: 664505 $ + * + * @since 4.0 + */ +public class HttpPut extends HttpEntityEnclosingRequestBase { + + public final static String METHOD_NAME = "PUT"; + + public HttpPut() { + super(); + } + + public HttpPut(final URI uri) { + super(); + setURI(uri); + } + + /** + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpPut(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + +} diff --git a/src/org/apache/http/client/methods/HttpRequestBase.java b/src/org/apache/http/client/methods/HttpRequestBase.java new file mode 100644 index 0000000..8938ea0 --- /dev/null +++ b/src/org/apache/http/client/methods/HttpRequestBase.java @@ -0,0 +1,182 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.client.utils.CloneUtils; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionReleaseTrigger; +import org.apache.http.message.AbstractHttpMessage; +import org.apache.http.message.BasicRequestLine; +import org.apache.http.message.HeaderGroup; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; + +/** + * Basic implementation of an HTTP request that can be modified. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public abstract class HttpRequestBase extends AbstractHttpMessage + implements HttpUriRequest, AbortableHttpRequest, Cloneable { + + private Lock abortLock; + + private boolean aborted; + + private URI uri; + private ClientConnectionRequest connRequest; + private ConnectionReleaseTrigger releaseTrigger; + + public HttpRequestBase() { + super(); + this.abortLock = new ReentrantLock(); + } + + public abstract String getMethod(); + + public ProtocolVersion getProtocolVersion() { + return HttpProtocolParams.getVersion(getParams()); + } + + public URI getURI() { + return this.uri; + } + + public RequestLine getRequestLine() { + String method = getMethod(); + ProtocolVersion ver = getProtocolVersion(); + URI uri = getURI(); + String uritext = null; + if (uri != null) { + uritext = uri.toASCIIString(); + } + if (uritext == null || uritext.length() == 0) { + uritext = "/"; + } + return new BasicRequestLine(method, uritext, ver); + } + + public void setURI(final URI uri) { + this.uri = uri; + } + + public void setConnectionRequest(final ClientConnectionRequest connRequest) + throws IOException { + this.abortLock.lock(); + try { + if (this.aborted) { + throw new IOException("Request already aborted"); + } + + this.releaseTrigger = null; + this.connRequest = connRequest; + } finally { + this.abortLock.unlock(); + } + } + + public void setReleaseTrigger(final ConnectionReleaseTrigger releaseTrigger) + throws IOException { + this.abortLock.lock(); + try { + if (this.aborted) { + throw new IOException("Request already aborted"); + } + + this.connRequest = null; + this.releaseTrigger = releaseTrigger; + } finally { + this.abortLock.unlock(); + } + } + + public void abort() { + ClientConnectionRequest localRequest; + ConnectionReleaseTrigger localTrigger; + + this.abortLock.lock(); + try { + if (this.aborted) { + return; + } + this.aborted = true; + + localRequest = connRequest; + localTrigger = releaseTrigger; + } finally { + this.abortLock.unlock(); + } + + // Trigger the callbacks outside of the lock, to prevent + // deadlocks in the scenario where the callbacks have + // their own locks that may be used while calling + // setReleaseTrigger or setConnectionRequest. + if (localRequest != null) { + localRequest.abortRequest(); + } + if (localTrigger != null) { + try { + localTrigger.abortConnection(); + } catch (IOException ex) { + // ignore + } + } + } + + public boolean isAborted() { + return this.aborted; + } + + @Override + public Object clone() throws CloneNotSupportedException { + HttpRequestBase clone = (HttpRequestBase) super.clone(); + clone.abortLock = new ReentrantLock(); + clone.aborted = false; + clone.releaseTrigger = null; + clone.connRequest = null; + clone.headergroup = (HeaderGroup) CloneUtils.clone(this.headergroup); + clone.params = (HttpParams) CloneUtils.clone(this.params); + return clone; + } + +} diff --git a/src/org/apache/http/client/methods/HttpTrace.java b/src/org/apache/http/client/methods/HttpTrace.java new file mode 100644 index 0000000..94f18ff --- /dev/null +++ b/src/org/apache/http/client/methods/HttpTrace.java @@ -0,0 +1,82 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpTrace.java $ + * $Revision: 664505 $ + * $Date: 2008-06-08 06:21:20 -0700 (Sun, 08 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; + +/** + * HTTP TRACE method. + * <p> + * The HTTP TRACE method is defined in section 9.6 of + * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: + * <blockquote> + * The TRACE method is used to invoke a remote, application-layer loop- + * back of the request message. The final recipient of the request + * SHOULD reflect the message received back to the client as the + * entity-body of a 200 (OK) response. The final recipient is either the + * origin server or the first proxy or gateway to receive a Max-Forwards + * value of zero (0) in the request (see section 14.31). A TRACE request + * MUST NOT include an entity. + * </blockquote> + * </p> + * + * @version $Revision: 664505 $ + * + * @since 4.0 + */ +public class HttpTrace extends HttpRequestBase { + + public final static String METHOD_NAME = "TRACE"; + + public HttpTrace() { + super(); + } + + public HttpTrace(final URI uri) { + super(); + setURI(uri); + } + + /** + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpTrace(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + +} diff --git a/src/org/apache/http/client/methods/HttpUriRequest.java b/src/org/apache/http/client/methods/HttpUriRequest.java new file mode 100644 index 0000000..56d064a --- /dev/null +++ b/src/org/apache/http/client/methods/HttpUriRequest.java @@ -0,0 +1,80 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpUriRequest.java $ + * $Revision: 659191 $ + * $Date: 2008-05-22 11:26:53 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.methods; + +import java.net.URI; + +import org.apache.http.HttpRequest; + +/** + * Extended version of the {@link HttpRequest} interface that provides + * convenience methods to access request properties such as request URI + * and method type. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 659191 $ + * + * @since 4.0 + */ +public interface HttpUriRequest extends HttpRequest { + + /** + * Returns the HTTP method this request uses, such as <code>GET</code>, + * <code>PUT</code>, <code>POST</code>, or other. + */ + String getMethod(); + + /** + * Returns the URI this request uses, such as + * <code>http://example.org/path/to/file</code>. + */ + URI getURI(); + + /** + * Aborts execution of the request. + * + * @throws UnsupportedOperationException if the abort operation + * is not supported / cannot be implemented. + */ + void abort() throws UnsupportedOperationException; + + /** + * Tests if the request execution has been aborted. + * + * @return <code>true</code> if the request execution has been aborted, + * <code>false</code> otherwise. + */ + boolean isAborted(); + +} diff --git a/src/org/apache/http/client/methods/package.html b/src/org/apache/http/client/methods/package.html new file mode 100644 index 0000000..c2a602a --- /dev/null +++ b/src/org/apache/http/client/methods/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Request implementations for the various HTTP methods like GET and POST. + +</body> +</html> diff --git a/src/org/apache/http/client/package.html b/src/org/apache/http/client/package.html new file mode 100644 index 0000000..64c7287 --- /dev/null +++ b/src/org/apache/http/client/package.html @@ -0,0 +1,41 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The API for client-side HTTP communication and +entry point to the <i>HttpClient</i> module. + +</body> +</html> diff --git a/src/org/apache/http/client/params/AllClientPNames.java b/src/org/apache/http/client/params/AllClientPNames.java new file mode 100644 index 0000000..e55bca7 --- /dev/null +++ b/src/org/apache/http/client/params/AllClientPNames.java @@ -0,0 +1,65 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/params/AllClientPNames.java $ + * $Revision: 576078 $ + * $Date: 2007-09-16 04:50:41 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.params; + + +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.auth.params.AuthPNames; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.conn.params.ConnManagerPNames; +import org.apache.http.conn.params.ConnConnectionPNames; +import org.apache.http.conn.params.ConnRoutePNames; + + +/** + * Collected parameter names for the HttpClient module. + * This interface combines the parameter definitions of the HttpClient + * module and all dependency modules or informational units. + * It does not define additional parameter names, but references + * other interfaces defining parameter names. + * <br/> + * This interface is meant as a navigation aid for developers. + * When referring to parameter names, you should use the interfaces + * in which the respective constants are actually defined. + * + * @version $Revision: 576078 $ + * + * @since 4.0 + */ +public interface AllClientPNames extends + CoreConnectionPNames, CoreProtocolPNames, + ClientPNames, AuthPNames, CookieSpecPNames, + ConnConnectionPNames, ConnManagerPNames, ConnRoutePNames { + + // no additional definitions +} + diff --git a/src/org/apache/http/client/params/AuthPolicy.java b/src/org/apache/http/client/params/AuthPolicy.java new file mode 100644 index 0000000..5bcdd38 --- /dev/null +++ b/src/org/apache/http/client/params/AuthPolicy.java @@ -0,0 +1,58 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/params/AuthPolicy.java $ + * $Revision: 534839 $ + * $Date: 2007-05-03 06:03:41 -0700 (Thu, 03 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.params; + +public final class AuthPolicy { + + private AuthPolicy() { + super(); + } + + /** + * The NTLM scheme is a proprietary Microsoft Windows Authentication + * protocol (considered to be the most secure among currently supported + * authentication schemes). + */ + public static final String NTLM = "NTLM"; + + /** + * Digest authentication scheme as defined in RFC2617. + */ + public static final String DIGEST = "Digest"; + + /** + * Basic authentication scheme as defined in RFC2617 (considered inherently + * insecure, but most widely supported) + */ + public static final String BASIC = "Basic"; + +} diff --git a/src/org/apache/http/client/params/ClientPNames.java b/src/org/apache/http/client/params/ClientPNames.java new file mode 100644 index 0000000..f98eeb7 --- /dev/null +++ b/src/org/apache/http/client/params/ClientPNames.java @@ -0,0 +1,139 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/params/ClientPNames.java $ + * $Revision: 659595 $ + * $Date: 2008-05-23 09:47:14 -0700 (Fri, 23 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.params; + + +/** + * Parameter names for the HttpClient module. + * This does not include parameters for informational units + * HttpAuth, HttpCookie, or HttpConn. + * + * @version $Revision: 659595 $ + * + * @since 4.0 + */ +public interface ClientPNames { + + /** + * Defines the class name of the default {@link org.apache.http.conn.ClientConnectionManager} + * <p> + * This parameter expects a value of type {@link String}. + * </p> + */ + public static final String CONNECTION_MANAGER_FACTORY_CLASS_NAME = "http.connection-manager.factory-class-name"; + + /** + * Defines the factory to create a default {@link org.apache.http.conn.ClientConnectionManager}. + * <p> + * This parameters expects a value of type {@link org.apache.http.conn.ClientConnectionManagerFactory}. + * </p> + */ + public static final String CONNECTION_MANAGER_FACTORY = "http.connection-manager.factory-object"; + + /** + * Defines whether redirects should be handled automatically + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + */ + public static final String HANDLE_REDIRECTS = "http.protocol.handle-redirects"; + + /** + * Defines whether relative redirects should be rejected. + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + */ + public static final String REJECT_RELATIVE_REDIRECT = "http.protocol.reject-relative-redirect"; + + /** + * Defines the maximum number of redirects to be followed. + * The limit on number of redirects is intended to prevent infinite loops. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + */ + public static final String MAX_REDIRECTS = "http.protocol.max-redirects"; + + /** + * Defines whether circular redirects (redirects to the same location) should be allowed. + * The HTTP spec is not sufficiently clear whether circular redirects are permitted, + * therefore optionally they can be enabled + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + */ + public static final String ALLOW_CIRCULAR_REDIRECTS = "http.protocol.allow-circular-redirects"; + + /** + * Defines whether authentication should be handled automatically. + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + */ + public static final String HANDLE_AUTHENTICATION = "http.protocol.handle-authentication"; + + /** + * Defines the name of the cookie specification to be used for HTTP state management. + * <p> + * This parameter expects a value of type {@link String}. + * </p> + */ + public static final String COOKIE_POLICY = "http.protocol.cookie-policy"; + + /** + * Defines the virtual host name. + * <p> + * This parameter expects a value of type {@link org.apache.http.HttpHost}. + * </p> + */ + public static final String VIRTUAL_HOST = "http.virtual-host"; + + /** + * Defines the request headers to be sent per default with each request. + * <p> + * This parameter expects a value of type {@link java.util.Collection}. The + * collection is expected to contain {@link org.apache.http.Header}s. + * </p> + */ + public static final String DEFAULT_HEADERS = "http.default-headers"; + + /** + * Defines the default host. The default value will be used if the target host is + * not explicitly specified in the request URI. + * <p> + * This parameter expects a value of type {@link org.apache.http.HttpHost}. + * </p> + */ + public static final String DEFAULT_HOST = "http.default-host"; + +} + diff --git a/src/org/apache/http/client/params/ClientParamBean.java b/src/org/apache/http/client/params/ClientParamBean.java new file mode 100644 index 0000000..76431a7 --- /dev/null +++ b/src/org/apache/http/client/params/ClientParamBean.java @@ -0,0 +1,92 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/params/ClientParamBean.java $ + * $Revision: 659595 $ + * $Date: 2008-05-23 09:47:14 -0700 (Fri, 23 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.params; + +import java.util.Collection; + +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.conn.ClientConnectionManagerFactory; +import org.apache.http.params.HttpAbstractParamBean; +import org.apache.http.params.HttpParams; + +public class ClientParamBean extends HttpAbstractParamBean { + + public ClientParamBean (final HttpParams params) { + super(params); + } + + public void setConnectionManagerFactoryClassName (final String factory) { + params.setParameter(ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME, factory); + } + + public void setConnectionManagerFactory(ClientConnectionManagerFactory factory) { + params.setParameter(ClientPNames.CONNECTION_MANAGER_FACTORY, factory); + } + + public void setHandleRedirects (final boolean handle) { + params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, handle); + } + + public void setRejectRelativeRedirect (final boolean reject) { + params.setBooleanParameter(ClientPNames.REJECT_RELATIVE_REDIRECT, reject); + } + + public void setMaxRedirects (final int maxRedirects) { + params.setIntParameter(ClientPNames.MAX_REDIRECTS, maxRedirects); + } + + public void setAllowCircularRedirects (final boolean allow) { + params.setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, allow); + } + + public void setHandleAuthentication (final boolean handle) { + params.setBooleanParameter(ClientPNames.HANDLE_AUTHENTICATION, handle); + } + + public void setCookiePolicy (final String policy) { + params.setParameter(ClientPNames.COOKIE_POLICY, policy); + } + + public void setVirtualHost (final HttpHost host) { + params.setParameter(ClientPNames.VIRTUAL_HOST, host); + } + + public void setDefaultHeaders (final Collection <Header> headers) { + params.setParameter(ClientPNames.DEFAULT_HEADERS, headers); + } + + public void setDefaultHost (final HttpHost host) { + params.setParameter(ClientPNames.DEFAULT_HOST, host); + } + +} diff --git a/src/org/apache/http/client/params/CookiePolicy.java b/src/org/apache/http/client/params/CookiePolicy.java new file mode 100644 index 0000000..04a131d --- /dev/null +++ b/src/org/apache/http/client/params/CookiePolicy.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/params/CookiePolicy.java $ + * $Revision: 613865 $ + * $Date: 2008-01-21 04:04:30 -0800 (Mon, 21 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.params; + +public final class CookiePolicy { + + /** + * The policy that provides high degree of compatibilty + * with common cookie management of popular HTTP agents. + */ + public static final String BROWSER_COMPATIBILITY = "compatibility"; + + /** + * The Netscape cookie draft compliant policy. + */ + public static final String NETSCAPE = "netscape"; + + /** + * The RFC 2109 compliant policy. + */ + public static final String RFC_2109 = "rfc2109"; + + /** + * The RFC 2965 compliant policy. + */ + public static final String RFC_2965 = "rfc2965"; + + /** + * The default 'best match' policy. + */ + public static final String BEST_MATCH = "best-match"; + + private CookiePolicy() { + super(); + } + +} diff --git a/src/org/apache/http/client/params/HttpClientParams.java b/src/org/apache/http/client/params/HttpClientParams.java new file mode 100644 index 0000000..c21e2b0 --- /dev/null +++ b/src/org/apache/http/client/params/HttpClientParams.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/params/HttpClientParams.java $ + * $Revision: 659595 $ + * $Date: 2008-05-23 09:47:14 -0700 (Fri, 23 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.params; + +import org.apache.http.params.HttpParams; + +/** + * An adaptor for accessing HTTP client parameters in {@link HttpParams}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 659595 $ + * + * @since 4.0 + */ +public class HttpClientParams { + + private HttpClientParams() { + super(); + } + + public static boolean isRedirecting(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getBooleanParameter + (ClientPNames.HANDLE_REDIRECTS, true); + } + + public static void setRedirecting(final HttpParams params, boolean value) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setBooleanParameter + (ClientPNames.HANDLE_REDIRECTS, value); + } + + public static boolean isAuthenticating(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getBooleanParameter + (ClientPNames.HANDLE_AUTHENTICATION, true); + } + + public static void setAuthenticating(final HttpParams params, boolean value) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setBooleanParameter + (ClientPNames.HANDLE_AUTHENTICATION, value); + } + + public static String getCookiePolicy(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + String cookiePolicy = (String) + params.getParameter(ClientPNames.COOKIE_POLICY); + if (cookiePolicy == null) { + return CookiePolicy.BEST_MATCH; + } + return cookiePolicy; + } + + public static void setCookiePolicy(final HttpParams params, final String cookiePolicy) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setParameter(ClientPNames.COOKIE_POLICY, cookiePolicy); + } + +} diff --git a/src/org/apache/http/client/params/package.html b/src/org/apache/http/client/params/package.html new file mode 100644 index 0000000..b66cdfb --- /dev/null +++ b/src/org/apache/http/client/params/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/params/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Parameters for configuring <i>HttpClient</i>. + +</body> +</html> diff --git a/src/org/apache/http/client/protocol/ClientContext.java b/src/org/apache/http/client/protocol/ClientContext.java new file mode 100644 index 0000000..1859f9e --- /dev/null +++ b/src/org/apache/http/client/protocol/ClientContext.java @@ -0,0 +1,52 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ClientContext.java $ + * $Revision: 658759 $ + * $Date: 2008-05-21 10:06:17 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.protocol; + + +/** + * {@link org.apache.http.protocol.HttpContext Context} + * attribute names for client. + */ +public interface ClientContext { + + public static final String COOKIESPEC_REGISTRY = "http.cookiespec-registry"; + public static final String AUTHSCHEME_REGISTRY = "http.authscheme-registry"; + public static final String COOKIE_STORE = "http.cookie-store"; + public static final String COOKIE_SPEC = "http.cookie-spec"; + public static final String COOKIE_ORIGIN = "http.cookie-origin"; + public static final String CREDS_PROVIDER = "http.auth.credentials-provider"; + public static final String TARGET_AUTH_STATE = "http.auth.target-scope"; + public static final String PROXY_AUTH_STATE = "http.auth.proxy-scope"; + public static final String AUTH_SCHEME_PREF = "http.auth.scheme-pref"; + public static final String USER_TOKEN = "http.user-token"; + +} diff --git a/src/org/apache/http/client/protocol/ClientContextConfigurer.java b/src/org/apache/http/client/protocol/ClientContextConfigurer.java new file mode 100644 index 0000000..f2ced63 --- /dev/null +++ b/src/org/apache/http/client/protocol/ClientContextConfigurer.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ClientContextConfigurer.java $ + * $Revision: 654886 $ + * $Date: 2008-05-09 10:06:12 -0700 (Fri, 09 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.protocol; + +import java.util.List; + +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.protocol.HttpContext; + +public class ClientContextConfigurer implements ClientContext { + + private final HttpContext context; + + public ClientContextConfigurer (final HttpContext context) { + if (context == null) + throw new IllegalArgumentException("HTTP context may not be null"); + this.context = context; + } + + public void setCookieSpecRegistry(final CookieSpecRegistry registry) { + this.context.setAttribute(COOKIESPEC_REGISTRY, registry); + } + + public void setAuthSchemeRegistry(final AuthSchemeRegistry registry) { + this.context.setAttribute(AUTHSCHEME_REGISTRY, registry); + } + + public void setCookieStore(final CookieStore store) { + this.context.setAttribute(COOKIE_STORE, store); + } + + public void setCredentialsProvider(final CredentialsProvider provider) { + this.context.setAttribute(CREDS_PROVIDER, provider); + } + + public void setAuthSchemePref(final List<String> list) { + this.context.setAttribute(AUTH_SCHEME_PREF, list); + } + +} diff --git a/src/org/apache/http/client/protocol/RequestAddCookies.java b/src/org/apache/http/client/protocol/RequestAddCookies.java new file mode 100644 index 0000000..0de8c40 --- /dev/null +++ b/src/org/apache/http/client/protocol/RequestAddCookies.java @@ -0,0 +1,192 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/RequestAddCookies.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.protocol; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.ProtocolException; +import org.apache.http.client.CookieStore; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.params.HttpClientParams; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; + +/** + * Request interceptor that matches cookies available in the current + * {@link CookieStore} to the request being executed and generates + * corresponding cookierequest headers. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class RequestAddCookies implements HttpRequestInterceptor { + + private final Log log = LogFactory.getLog(getClass()); + + public RequestAddCookies() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + // Obtain cookie store + CookieStore cookieStore = (CookieStore) context.getAttribute( + ClientContext.COOKIE_STORE); + if (cookieStore == null) { + this.log.info("Cookie store not available in HTTP context"); + return; + } + + // Obtain the registry of cookie specs + CookieSpecRegistry registry= (CookieSpecRegistry) context.getAttribute( + ClientContext.COOKIESPEC_REGISTRY); + if (registry == null) { + this.log.info("CookieSpec registry not available in HTTP context"); + return; + } + + // Obtain the target host (required) + HttpHost targetHost = (HttpHost) context.getAttribute( + ExecutionContext.HTTP_TARGET_HOST); + if (targetHost == null) { + throw new IllegalStateException("Target host not specified in HTTP context"); + } + + // Obtain the client connection (required) + ManagedClientConnection conn = (ManagedClientConnection) context.getAttribute( + ExecutionContext.HTTP_CONNECTION); + if (conn == null) { + throw new IllegalStateException("Client connection not specified in HTTP context"); + } + + String policy = HttpClientParams.getCookiePolicy(request.getParams()); + if (this.log.isDebugEnabled()) { + this.log.debug("CookieSpec selected: " + policy); + } + + URI requestURI; + if (request instanceof HttpUriRequest) { + requestURI = ((HttpUriRequest) request).getURI(); + } else { + try { + requestURI = new URI(request.getRequestLine().getUri()); + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid request URI: " + + request.getRequestLine().getUri(), ex); + } + } + + String hostName = targetHost.getHostName(); + int port = targetHost.getPort(); + if (port < 0) { + port = conn.getRemotePort(); + } + + CookieOrigin cookieOrigin = new CookieOrigin( + hostName, + port, + requestURI.getPath(), + conn.isSecure()); + + // Get an instance of the selected cookie policy + CookieSpec cookieSpec = registry.getCookieSpec(policy, request.getParams()); + // Get all cookies available in the HTTP state + List<Cookie> cookies = new ArrayList<Cookie>(cookieStore.getCookies()); + // Find cookies matching the given origin + List<Cookie> matchedCookies = new ArrayList<Cookie>(); + for (Cookie cookie : cookies) { + if (cookieSpec.match(cookie, cookieOrigin)) { + if (this.log.isDebugEnabled()) { + this.log.debug("Cookie " + cookie + " match " + cookieOrigin); + } + matchedCookies.add(cookie); + } + } + // Generate Cookie request headers + if (!matchedCookies.isEmpty()) { + List<Header> headers = cookieSpec.formatCookies(matchedCookies); + for (Header header : headers) { + request.addHeader(header); + } + } + + int ver = cookieSpec.getVersion(); + if (ver > 0) { + boolean needVersionHeader = false; + for (Cookie cookie : matchedCookies) { + if (ver != cookie.getVersion()) { + needVersionHeader = true; + } + } + + if (needVersionHeader) { + Header header = cookieSpec.getVersionHeader(); + if (header != null) { + // Advertise cookie version support + request.addHeader(header); + } + } + } + + // Stick the CookieSpec and CookieOrigin instances to the HTTP context + // so they could be obtained by the response interceptor + context.setAttribute(ClientContext.COOKIE_SPEC, cookieSpec); + context.setAttribute(ClientContext.COOKIE_ORIGIN, cookieOrigin); + } + +} diff --git a/src/org/apache/http/client/protocol/RequestDefaultHeaders.java b/src/org/apache/http/client/protocol/RequestDefaultHeaders.java new file mode 100644 index 0000000..27d5cc7 --- /dev/null +++ b/src/org/apache/http/client/protocol/RequestDefaultHeaders.java @@ -0,0 +1,74 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/RequestDefaultHeaders.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.protocol; + +import java.io.IOException; +import java.util.Collection; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.protocol.HttpContext; + +/** + * Request interceptor that adds default request headers. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 653041 $ + * + * @since 4.0 + */ +public class RequestDefaultHeaders implements HttpRequestInterceptor { + + public RequestDefaultHeaders() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + // Add default headers + Collection<?> defHeaders = (Collection<?>) request.getParams().getParameter( + ClientPNames.DEFAULT_HEADERS); + if (defHeaders != null) { + for (Object defHeader : defHeaders) { + request.addHeader((Header) defHeader); + } + } + } + +} diff --git a/src/org/apache/http/client/protocol/RequestProxyAuthentication.java b/src/org/apache/http/client/protocol/RequestProxyAuthentication.java new file mode 100644 index 0000000..b4dfe76 --- /dev/null +++ b/src/org/apache/http/client/protocol/RequestProxyAuthentication.java @@ -0,0 +1,104 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/RequestProxyAuthentication.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.protocol; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class RequestProxyAuthentication implements HttpRequestInterceptor { + + private final Log log = LogFactory.getLog(getClass()); + + public RequestProxyAuthentication() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + if (request.containsHeader(AUTH.PROXY_AUTH_RESP)) { + return; + } + + // Obtain authentication state + AuthState authState = (AuthState) context.getAttribute( + ClientContext.PROXY_AUTH_STATE); + if (authState == null) { + return; + } + + AuthScheme authScheme = authState.getAuthScheme(); + if (authScheme == null) { + return; + } + + Credentials creds = authState.getCredentials(); + if (creds == null) { + this.log.debug("User credentials not available"); + return; + } + if (authState.getAuthScope() != null || !authScheme.isConnectionBased()) { + try { + request.addHeader(authScheme.authenticate(creds, request)); + } catch (AuthenticationException ex) { + if (this.log.isErrorEnabled()) { + this.log.error("Proxy authentication error: " + ex.getMessage()); + } + } + } + } + +} diff --git a/src/org/apache/http/client/protocol/RequestTargetAuthentication.java b/src/org/apache/http/client/protocol/RequestTargetAuthentication.java new file mode 100644 index 0000000..c140183 --- /dev/null +++ b/src/org/apache/http/client/protocol/RequestTargetAuthentication.java @@ -0,0 +1,105 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/RequestTargetAuthentication.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.protocol; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class RequestTargetAuthentication implements HttpRequestInterceptor { + + private final Log log = LogFactory.getLog(getClass()); + + public RequestTargetAuthentication() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + if (request.containsHeader(AUTH.WWW_AUTH_RESP)) { + return; + } + + // Obtain authentication state + AuthState authState = (AuthState) context.getAttribute( + ClientContext.TARGET_AUTH_STATE); + if (authState == null) { + return; + } + + AuthScheme authScheme = authState.getAuthScheme(); + if (authScheme == null) { + return; + } + + Credentials creds = authState.getCredentials(); + if (creds == null) { + this.log.debug("User credentials not available"); + return; + } + + if (authState.getAuthScope() != null || !authScheme.isConnectionBased()) { + try { + request.addHeader(authScheme.authenticate(creds, request)); + } catch (AuthenticationException ex) { + if (this.log.isErrorEnabled()) { + this.log.error("Authentication error: " + ex.getMessage()); + } + } + } + } + +} diff --git a/src/org/apache/http/client/protocol/ResponseProcessCookies.java b/src/org/apache/http/client/protocol/ResponseProcessCookies.java new file mode 100644 index 0000000..0689e93 --- /dev/null +++ b/src/org/apache/http/client/protocol/ResponseProcessCookies.java @@ -0,0 +1,146 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/ResponseProcessCookies.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.protocol; + +import java.io.IOException; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HeaderIterator; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.client.CookieStore; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.protocol.HttpContext; + +/** + * Response interceptor that populates the current {@link CookieStore} with data + * contained in response cookies received in the given the HTTP response. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class ResponseProcessCookies implements HttpResponseInterceptor { + + private final Log log = LogFactory.getLog(getClass()); + + public ResponseProcessCookies() { + super(); + } + + public void process(final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + // Obtain cookie store + CookieStore cookieStore = (CookieStore) context.getAttribute( + ClientContext.COOKIE_STORE); + if (cookieStore == null) { + this.log.info("Cookie store not available in HTTP context"); + return; + } + // Obtain actual CookieSpec instance + CookieSpec cookieSpec = (CookieSpec) context.getAttribute( + ClientContext.COOKIE_SPEC); + if (cookieSpec == null) { + this.log.info("CookieSpec not available in HTTP context"); + return; + } + // Obtain actual CookieOrigin instance + CookieOrigin cookieOrigin = (CookieOrigin) context.getAttribute( + ClientContext.COOKIE_ORIGIN); + if (cookieOrigin == null) { + this.log.info("CookieOrigin not available in HTTP context"); + return; + } + HeaderIterator it = response.headerIterator(SM.SET_COOKIE); + processCookies(it, cookieSpec, cookieOrigin, cookieStore); + + // see if the cookie spec supports cookie versioning. + if (cookieSpec.getVersion() > 0) { + // process set-cookie2 headers. + // Cookie2 will replace equivalent Cookie instances + it = response.headerIterator(SM.SET_COOKIE2); + processCookies(it, cookieSpec, cookieOrigin, cookieStore); + } + } + + private void processCookies( + final HeaderIterator iterator, + final CookieSpec cookieSpec, + final CookieOrigin cookieOrigin, + final CookieStore cookieStore) { + while (iterator.hasNext()) { + Header header = iterator.nextHeader(); + try { + List<Cookie> cookies = cookieSpec.parse(header, cookieOrigin); + for (Cookie cookie : cookies) { + try { + cookieSpec.validate(cookie, cookieOrigin); + cookieStore.addCookie(cookie); + + if (this.log.isDebugEnabled()) { + this.log.debug("Cookie accepted: \"" + + cookie + "\". "); + } + } catch (MalformedCookieException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Cookie rejected: \"" + + cookie + "\". " + ex.getMessage()); + } + } + } + } catch (MalformedCookieException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Invalid cookie header: \"" + + header + "\". " + ex.getMessage()); + } + } + } + } + +} diff --git a/src/org/apache/http/client/protocol/package.html b/src/org/apache/http/client/protocol/package.html new file mode 100644 index 0000000..43dd0d6 --- /dev/null +++ b/src/org/apache/http/client/protocol/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/protocol/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Additional request and response interceptors. + +</body> +</html> diff --git a/src/org/apache/http/client/utils/CloneUtils.java b/src/org/apache/http/client/utils/CloneUtils.java new file mode 100644 index 0000000..fec534b --- /dev/null +++ b/src/org/apache/http/client/utils/CloneUtils.java @@ -0,0 +1,75 @@ +/* + * $HeadURL:$ + * $Revision:$ + * $Date:$ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.client.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A collection of utilities to workaround limitations of Java clone framework. + */ +public class CloneUtils { + + public static Object clone(final Object obj) throws CloneNotSupportedException { + if (obj == null) { + return null; + } + if (obj instanceof Cloneable) { + Class<?> clazz = obj.getClass (); + Method m; + try { + m = clazz.getMethod("clone", (Class[]) null); + } catch (NoSuchMethodException ex) { + throw new NoSuchMethodError(ex.getMessage()); + } + try { + return m.invoke(obj, (Object []) null); + } catch (InvocationTargetException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof CloneNotSupportedException) { + throw ((CloneNotSupportedException) cause); + } else { + throw new Error("Unexpected exception", cause); + } + } catch (IllegalAccessException ex) { + throw new IllegalAccessError(ex.getMessage()); + } + } else { + throw new CloneNotSupportedException(); + } + } + + /** + * This class should not be instantiated. + */ + private CloneUtils() { + } + +} diff --git a/src/org/apache/http/client/utils/URIUtils.java b/src/org/apache/http/client/utils/URIUtils.java new file mode 100644 index 0000000..1cbb9af --- /dev/null +++ b/src/org/apache/http/client/utils/URIUtils.java @@ -0,0 +1,209 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/utils/URIUtils.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.client.utils; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.http.HttpHost; + +/** + * A collection of utilities for {@link URI URIs}, to workaround + * bugs within the class or for ease-of-use features. + */ +public class URIUtils { + + /** + * Constructs a {@link URI} using all the parameters. This should be + * used instead of + * {@link URI#URI(String, String, String, int, String, String, String)} + * or any of the other URI multi-argument URI constructors. + * + * See <a + * href="https://issues.apache.org/jira/browse/HTTPCLIENT-730">HTTPCLIENT-730</a> + * for more information. + * + * @param scheme + * Scheme name + * @param host + * Host name + * @param port + * Port number + * @param path + * Path + * @param query + * Query + * @param fragment + * Fragment + * + * @throws URISyntaxException + * If both a scheme and a path are given but the path is + * relative, if the URI string constructed from the given + * components violates RFC 2396, or if the authority + * component of the string is present but cannot be parsed + * as a server-based authority + */ + public static URI createURI( + final String scheme, + final String host, + int port, + final String path, + final String query, + final String fragment) throws URISyntaxException { + + StringBuilder buffer = new StringBuilder(); + if (host != null) { + if (scheme != null) { + buffer.append(scheme); + buffer.append("://"); + } + buffer.append(host); + if (port > 0) { + buffer.append(':'); + buffer.append(port); + } + } + if (path == null || !path.startsWith("/")) { + buffer.append('/'); + } + if (path != null) { + buffer.append(path); + } + if (query != null) { + buffer.append('?'); + buffer.append(query); + } + if (fragment != null) { + buffer.append('#'); + buffer.append(fragment); + } + return new URI(buffer.toString()); + } + + /** + * A convenience method for creating a new {@link URI} whose scheme, host + * and port are taken from the target host, but whose path, query and + * fragment are taken from the existing URI. The fragment is only used if + * dropFragment is false. + * + * @param uri + * Contains the path, query and fragment to use. + * @param target + * Contains the scheme, host and port to use. + * @param dropFragment + * True if the fragment should not be copied. + * + * @throws URISyntaxException + * If the resulting URI is invalid. + */ + public static URI rewriteURI( + final URI uri, + final HttpHost target, + boolean dropFragment) throws URISyntaxException { + if (uri == null) { + throw new IllegalArgumentException("URI may nor be null"); + } + if (target != null) { + return URIUtils.createURI( + target.getSchemeName(), + target.getHostName(), + target.getPort(), + uri.getRawPath(), + uri.getRawQuery(), + dropFragment ? null : uri.getRawFragment()); + } else { + return URIUtils.createURI( + null, + null, + -1, + uri.getRawPath(), + uri.getRawQuery(), + dropFragment ? null : uri.getRawFragment()); + } + } + + /** + * A convenience method for + * {@link URIUtils#rewriteURI(URI, HttpHost, boolean)} that always keeps the + * fragment. + */ + public static URI rewriteURI( + final URI uri, + final HttpHost target) throws URISyntaxException { + return rewriteURI(uri, target, false); + } + + /** + * Resolves a URI reference against a base URI. Work-around for bug in + * java.net.URI (<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>) + * + * @param baseURI the base URI + * @param reference the URI reference + * @return the resulting URI + */ + public static URI resolve(final URI baseURI, final String reference) { + return URIUtils.resolve(baseURI, URI.create(reference)); + } + + /** + * Resolves a URI reference against a base URI. Work-around for bug in + * java.net.URI (<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>) + * + * @param baseURI the base URI + * @param reference the URI reference + * @return the resulting URI + */ + public static URI resolve(final URI baseURI, URI reference){ + if (baseURI == null) { + throw new IllegalArgumentException("Base URI may nor be null"); + } + if (reference == null) { + throw new IllegalArgumentException("Reference URI may nor be null"); + } + boolean emptyReference = reference.toString().length() == 0; + if (emptyReference) { + reference = URI.create("#"); + } + URI resolved = baseURI.resolve(reference); + if (emptyReference) { + String resolvedString = resolved.toString(); + resolved = URI.create(resolvedString.substring(0, + resolvedString.indexOf('#'))); + } + return resolved; + } + + /** + * This class should not be instantiated. + */ + private URIUtils() { + } + +} diff --git a/src/org/apache/http/client/utils/URLEncodedUtils.java b/src/org/apache/http/client/utils/URLEncodedUtils.java new file mode 100644 index 0000000..8b08f90 --- /dev/null +++ b/src/org/apache/http/client/utils/URLEncodedUtils.java @@ -0,0 +1,191 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java $ + * $Revision: 655107 $ + * $Date: 2008-05-10 08:20:42 -0700 (Sat, 10 May 2008) $ + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.client.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Scanner; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.EntityUtils; + +/** + * A collection of utilities for encoding URLs. + */ +public class URLEncodedUtils { + + public static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; + private static final String PARAMETER_SEPARATOR = "&"; + private static final String NAME_VALUE_SEPARATOR = "="; + + /** + * Returns a list of {@link NameValuePair NameValuePairs} as built from the + * URI's query portion. For example, a URI of + * http://example.org/path/to/file?a=1&b=2&c=3 would return a list of three + * NameValuePairs, one for a=1, one for b=2, and one for c=3. + * <p> + * This is typically useful while parsing an HTTP PUT. + * + * @param uri + * uri to parse + * @param encoding + * encoding to use while parsing the query + */ + public static List <NameValuePair> parse (final URI uri, final String encoding) { + List <NameValuePair> result = Collections.emptyList(); + final String query = uri.getRawQuery(); + if (query != null && query.length() > 0) { + result = new ArrayList <NameValuePair>(); + parse(result, new Scanner(query), encoding); + } + return result; + } + + /** + * Returns a list of {@link NameValuePair NameValuePairs} as parsed from an + * {@link HttpEntity}. The encoding is taken from the entity's + * Content-Encoding header. + * <p> + * This is typically used while parsing an HTTP POST. + * + * @param entity + * The entity to parse + * @throws IOException + * If there was an exception getting the entity's data. + */ + public static List <NameValuePair> parse ( + final HttpEntity entity) throws IOException { + List <NameValuePair> result = Collections.emptyList(); + if (isEncoded(entity)) { + final String content = EntityUtils.toString(entity); + final Header encoding = entity.getContentEncoding(); + if (content != null && content.length() > 0) { + result = new ArrayList <NameValuePair>(); + parse(result, new Scanner(content), + encoding != null ? encoding.getValue() : null); + } + } + return result; + } + + /** + * Returns true if the entity's Content-Type header is + * <code>application/x-www-form-urlencoded</code>. + */ + public static boolean isEncoded (final HttpEntity entity) { + final Header contentType = entity.getContentType(); + return (contentType != null && contentType.getValue().equalsIgnoreCase(CONTENT_TYPE)); + } + + /** + * Adds all parameters within the Scanner to the list of + * <code>parameters</code>, as encoded by <code>encoding</code>. For + * example, a scanner containing the string <code>a=1&b=2&c=3</code> would + * add the {@link NameValuePair NameValuePairs} a=1, b=2, and c=3 to the + * list of parameters. + * + * @param parameters + * List to add parameters to. + * @param scanner + * Input that contains the parameters to parse. + * @param encoding + * Encoding to use when decoding the parameters. + */ + public static void parse ( + final List <NameValuePair> parameters, + final Scanner scanner, + final String encoding) { + scanner.useDelimiter(PARAMETER_SEPARATOR); + while (scanner.hasNext()) { + final String[] nameValue = scanner.next().split(NAME_VALUE_SEPARATOR); + if (nameValue.length == 0 || nameValue.length > 2) + throw new IllegalArgumentException("bad parameter"); + + final String name = decode(nameValue[0], encoding); + String value = null; + if (nameValue.length == 2) + value = decode(nameValue[1], encoding); + parameters.add(new BasicNameValuePair(name, value)); + } + } + + /** + * Returns a String that is suitable for use as an <code>application/x-www-form-urlencoded</code> + * list of parameters in an HTTP PUT or HTTP POST. + * + * @param parameters The parameters to include. + * @param encoding The encoding to use. + */ + public static String format ( + final List <? extends NameValuePair> parameters, + final String encoding) { + final StringBuilder result = new StringBuilder(); + for (final NameValuePair parameter : parameters) { + final String encodedName = encode(parameter.getName(), encoding); + final String value = parameter.getValue(); + final String encodedValue = value != null ? encode(value, encoding) : ""; + if (result.length() > 0) + result.append(PARAMETER_SEPARATOR); + result.append(encodedName); + result.append(NAME_VALUE_SEPARATOR); + result.append(encodedValue); + } + return result.toString(); + } + + private static String decode (final String content, final String encoding) { + try { + return URLDecoder.decode(content, + encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET); + } catch (UnsupportedEncodingException problem) { + throw new IllegalArgumentException(problem); + } + } + + private static String encode (final String content, final String encoding) { + try { + return URLEncoder.encode(content, + encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET); + } catch (UnsupportedEncodingException problem) { + throw new IllegalArgumentException(problem); + } + } + +} diff --git a/src/org/apache/http/client/utils/package.html b/src/org/apache/http/client/utils/package.html new file mode 100644 index 0000000..7abeb93 --- /dev/null +++ b/src/org/apache/http/client/utils/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/utils/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Helpers and utility classes for <i>HttpClient</i>. + +</body> +</html> diff --git a/src/org/apache/http/conn/BasicEofSensorWatcher.java b/src/org/apache/http/conn/BasicEofSensorWatcher.java new file mode 100644 index 0000000..9a9f3c5 --- /dev/null +++ b/src/org/apache/http/conn/BasicEofSensorWatcher.java @@ -0,0 +1,121 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/BasicEofSensorWatcher.java $ + * $Revision $ + * $Date: 2008-06-27 12:49:20 -0700 (Fri, 27 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.InputStream; +import java.io.IOException; + + +/** + * Basic implementation of {@link EofSensorWatcher EofSensorWatcher}. + * The underlying connection is released on close or EOF. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672367 $ + * + * @since 4.0 + */ +public class BasicEofSensorWatcher implements EofSensorWatcher { + + + /** The connection to auto-release. */ + protected ManagedClientConnection managedConn; + + /** Whether to keep the connection alive. */ + protected boolean attemptReuse; + + + + /** + * Creates a new watcher for auto-releasing a connection. + * + * @param conn the connection to auto-release + * @param reuse whether the connection should be re-used + */ + public BasicEofSensorWatcher(ManagedClientConnection conn, + boolean reuse) { + if (conn == null) + throw new IllegalArgumentException + ("Connection may not be null."); + + managedConn = conn; + attemptReuse = reuse; + } + + + // non-javadoc, see interface EofSensorWatcher + public boolean eofDetected(InputStream wrapped) + throws IOException { + + try { + if (attemptReuse) { + // there may be some cleanup required, such as + // reading trailers after the response body: + wrapped.close(); + managedConn.markReusable(); + } + } finally { + managedConn.releaseConnection(); + } + return false; + } + + + // non-javadoc, see interface EofSensorWatcher + public boolean streamClosed(InputStream wrapped) + throws IOException { + + try { + if (attemptReuse) { + // this assumes that closing the stream will + // consume the remainder of the response body: + wrapped.close(); + managedConn.markReusable(); + } + } finally { + managedConn.releaseConnection(); + } + return false; + } + + + // non-javadoc, see interface EofSensorWatcher + public boolean streamAbort(InputStream wrapped) + throws IOException { + + managedConn.abortConnection(); + return false; + } + +} // class BasicEofSensorWatcher diff --git a/src/org/apache/http/conn/BasicManagedEntity.java b/src/org/apache/http/conn/BasicManagedEntity.java new file mode 100644 index 0000000..9719e1a --- /dev/null +++ b/src/org/apache/http/conn/BasicManagedEntity.java @@ -0,0 +1,220 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/BasicManagedEntity.java $ + * $Revision $ + * $Date: 2008-06-27 12:49:20 -0700 (Fri, 27 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.entity.HttpEntityWrapper; + + +/** + * An entity that releases a {@link ManagedClientConnection connection}. + * A {@link ManagedClientConnection} will + * typically <i>not</i> return a managed entity, but you can replace + * the unmanaged entity in the response with a managed one. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672367 $ + * + * @since 4.0 + */ +public class BasicManagedEntity extends HttpEntityWrapper + implements ConnectionReleaseTrigger, EofSensorWatcher { + + /** The connection to release. */ + protected ManagedClientConnection managedConn; + + /** Whether to keep the connection alive. */ + protected final boolean attemptReuse; + + + /** + * Creates a new managed entity that can release a connection. + * + * @param entity the entity of which to wrap the content. + * Note that the argument entity can no longer be used + * afterwards, since the content will be taken by this + * managed entity. + * @param conn the connection to release + * @param reuse whether the connection should be re-used + */ + public BasicManagedEntity(HttpEntity entity, + ManagedClientConnection conn, + boolean reuse) { + super(entity); + + if (conn == null) + throw new IllegalArgumentException + ("Connection may not be null."); + + this.managedConn = conn; + this.attemptReuse = reuse; + } + + + // non-javadoc, see interface HttpEntity + @Override + public boolean isRepeatable() { + return false; + } + + + // non-javadoc, see interface HttpEntity + @Override + public InputStream getContent() throws IOException { + + return new EofSensorInputStream(wrappedEntity.getContent(), this); + } + + + // non-javadoc, see interface HttpEntity + @Override + public void consumeContent() throws IOException { + + if (managedConn == null) + return; + + try { + if (attemptReuse) { + // this will not trigger a callback from EofSensorInputStream + wrappedEntity.consumeContent(); + managedConn.markReusable(); + } + } finally { + releaseManagedConnection(); + } + } + + + // non-javadoc, see interface HttpEntity + @Override + public void writeTo(final OutputStream outstream) throws IOException { + super.writeTo(outstream); + consumeContent(); + } + + + // non-javadoc, see interface ConnectionReleaseTrigger + public void releaseConnection() + throws IOException { + + this.consumeContent(); + } + + + // non-javadoc, see interface ConnectionReleaseTrigger + public void abortConnection() + throws IOException { + + if (managedConn != null) { + try { + managedConn.abortConnection(); + } finally { + managedConn = null; + } + } + } + + + // non-javadoc, see interface EofSensorWatcher + public boolean eofDetected(InputStream wrapped) + throws IOException { + + try { + if (attemptReuse && (managedConn != null)) { + // there may be some cleanup required, such as + // reading trailers after the response body: + wrapped.close(); + managedConn.markReusable(); + } + } finally { + releaseManagedConnection(); + } + return false; + } + + + // non-javadoc, see interface EofSensorWatcher + public boolean streamClosed(InputStream wrapped) + throws IOException { + + try { + if (attemptReuse && (managedConn != null)) { + // this assumes that closing the stream will + // consume the remainder of the response body: + wrapped.close(); + managedConn.markReusable(); + } + } finally { + releaseManagedConnection(); + } + return false; + } + + + // non-javadoc, see interface EofSensorWatcher + public boolean streamAbort(InputStream wrapped) + throws IOException { + + if (managedConn != null) { + managedConn.abortConnection(); + } + return false; + } + + + /** + * Releases the connection gracefully. + * The connection attribute will be nullified. + * Subsequent invocations are no-ops. + * + * @throws IOException in case of an IO problem. + * The connection attribute will be nullified anyway. + */ + protected void releaseManagedConnection() + throws IOException { + + if (managedConn != null) { + try { + managedConn.releaseConnection(); + } finally { + managedConn = null; + } + } + } + +} // class BasicManagedEntity diff --git a/src/org/apache/http/conn/ClientConnectionManager.java b/src/org/apache/http/conn/ClientConnectionManager.java new file mode 100644 index 0000000..e3375e1 --- /dev/null +++ b/src/org/apache/http/conn/ClientConnectionManager.java @@ -0,0 +1,129 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ClientConnectionManager.java $ + * $Revision: 671717 $ + * $Date: 2008-06-25 21:03:24 -0700 (Wed, 25 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + + +import java.util.concurrent.TimeUnit; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.scheme.SchemeRegistry; + +/** + * Management interface for {@link ManagedClientConnection client connections}. + * + * @author Michael Becke + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 671717 $ + * + * @since 4.0 + */ +public interface ClientConnectionManager { + + /** + * Obtains the scheme registry used by this manager. + * + * @return the scheme registry, never <code>null</code> + */ + SchemeRegistry getSchemeRegistry() + ; + + + /** + * Returns a new {@link ClientConnectionRequest}, from which a + * {@link ManagedClientConnection} can be obtained or the request can be + * aborted. + */ + ClientConnectionRequest requestConnection(HttpRoute route, Object state) + ; + + + /** + * Releases a connection for use by others. + * You may optionally specify how long the connection is valid + * to be reused. Values <= 0 are considered to be valid forever. + * If the connection is not marked as reusable, the connection will + * not be reused regardless of the valid duration. + * + * If the connection has been released before, + * the call will be ignored. + * + * @param conn the connection to release + * @param validDuration the duration of time this connection is valid for reuse + * @param timeUnit the unit of time validDuration is measured in + * + * @see #closeExpiredConnections() + */ + void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) + ; + + + /** + * Closes idle connections in the pool. + * Open connections in the pool that have not been used for the + * timespan given by the argument will be closed. + * Currently allocated connections are not subject to this method. + * Times will be checked with milliseconds precision + * + * All expired connections will also be closed. + * + * @param idletime the idle time of connections to be closed + * @param tunit the unit for the <code>idletime</code> + * + * @see #closeExpiredConnections() + */ + void closeIdleConnections(long idletime, TimeUnit tunit) + ; + + /** + * Closes all expired connections in the pool. + * Open connections in the pool that have not been used for + * the timespan defined when the connection was released will be closed. + * Currently allocated connections are not subject to this method. + * Times will be checked with milliseconds precision. + */ + void closeExpiredConnections(); + + /** + * Shuts down this connection manager and releases allocated resources. + * This includes closing all connections, whether they are currently + * used or not. + */ + void shutdown() + ; + + +} // interface ClientConnectionManager diff --git a/src/org/apache/http/conn/ClientConnectionManagerFactory.java b/src/org/apache/http/conn/ClientConnectionManagerFactory.java new file mode 100644 index 0000000..4bedc4e --- /dev/null +++ b/src/org/apache/http/conn/ClientConnectionManagerFactory.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ClientConnectionManagerFactory.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.params.HttpParams; + +/** + * A factory for creating new {@link ClientConnectionManager} instances. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface ClientConnectionManagerFactory { + + ClientConnectionManager newInstance( + HttpParams params, + SchemeRegistry schemeRegistry); + +} diff --git a/src/org/apache/http/conn/ClientConnectionOperator.java b/src/org/apache/http/conn/ClientConnectionOperator.java new file mode 100644 index 0000000..980b867 --- /dev/null +++ b/src/org/apache/http/conn/ClientConnectionOperator.java @@ -0,0 +1,120 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ClientConnectionOperator.java $ + * $Revision: 645850 $ + * $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.IOException; +import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.conn.scheme.SocketFactory; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; + + + +/** + * Interface for opening {@link OperatedClientConnection connections}. + * This interface encapsulates the logic to create sockets and to + * open or update the connection with the new socket. + * Implementations will most likely make use of + * {@link SocketFactory socket factories}. + * <br/> + * The methods in this interface allow the creation of plain and layered + * sockets. Creating a tunnelled connection through a proxy, however, + * is not within the scope of the operator. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 645850 $ $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $ + * + * @since 4.0 + */ +public interface ClientConnectionOperator { + + + /** + * Creates a new connection that can be operated. + * + * @return a new, unopened connection for use with this operator + */ + OperatedClientConnection createConnection() + ; + + + /** + * Opens a connection to the given target host. + * + * @param conn the connection to open + * @param target the target host to connect to + * @param local the local address to route from, or + * <code>null</code> for the default + * @param context the context for the connection + * @param params the parameters for the connection + * + * @throws IOException in case of a problem + */ + void openConnection(OperatedClientConnection conn, + HttpHost target, + InetAddress local, + HttpContext context, + HttpParams params) + throws IOException + ; + + + /** + * Updates a connection with a layered secure connection. + * The typical use of this method is to update a tunnelled plain + * connection (HTTP) to a secure TLS/SSL connection (HTTPS). + * + * @param conn the open connection to update + * @param target the target host for the updated connection. + * The connection must already be open or tunnelled + * to the host and port, but the scheme of the target + * will be used to create a layered connection. + * @param context the context for the connection + * @param params the parameters for the updated connection + * + * @throws IOException in case of a problem + */ + void updateSecureConnection(OperatedClientConnection conn, + HttpHost target, + HttpContext context, + HttpParams params) + throws IOException + ; + + +} // interface ClientConnectionOperator + diff --git a/src/org/apache/http/conn/ClientConnectionRequest.java b/src/org/apache/http/conn/ClientConnectionRequest.java new file mode 100644 index 0000000..6ba02d0 --- /dev/null +++ b/src/org/apache/http/conn/ClientConnectionRequest.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ClientConnectionRequest.java $ + * $Revision: 651815 $ + * $Date: 2008-04-26 04:14:43 -0700 (Sat, 26 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.util.concurrent.TimeUnit; + +/** + * Encapsulates a request for a {@link ManagedClientConnection}. + */ +public interface ClientConnectionRequest { + + /** + * Obtains a connection within a given time. + * This method will block until a connection becomes available, + * the timeout expires, or the connection manager is + * {@link ClientConnectionManager#shutdown() shut down}. + * Timeouts are handled with millisecond precision. + * + * If {@link #abortRequest()} is called while this is blocking or + * before this began, an {@link InterruptedException} will + * be thrown. + * + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return a connection that can be used to communicate + * along the given route + * + * @throws ConnectionPoolTimeoutException + * in case of a timeout + * @throws InterruptedException + * if the calling thread is interrupted while waiting + */ + ManagedClientConnection getConnection(long timeout, TimeUnit tunit) + throws InterruptedException, ConnectionPoolTimeoutException; + + /** + * Aborts the call to {@link #getConnection(long, TimeUnit)}, + * causing it to throw an {@link InterruptedException}. + */ + void abortRequest(); + +} diff --git a/src/org/apache/http/conn/ConnectTimeoutException.java b/src/org/apache/http/conn/ConnectTimeoutException.java new file mode 100644 index 0000000..83a731a --- /dev/null +++ b/src/org/apache/http/conn/ConnectTimeoutException.java @@ -0,0 +1,64 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ConnectTimeoutException.java $ + * $Revision: 617645 $ + * $Date: 2008-02-01 13:05:31 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.InterruptedIOException; + +/** + * A timeout while connecting to an HTTP server or waiting for an + * available connection from an HttpConnectionManager. + * + * @author <a href="mailto:laura@lwerner.org">Laura Werner</a> + * + * @since 4.0 + */ +public class ConnectTimeoutException extends InterruptedIOException { + + private static final long serialVersionUID = -4816682903149535989L; + + /** + * Creates a ConnectTimeoutException with a <tt>null</tt> detail message. + */ + public ConnectTimeoutException() { + super(); + } + + /** + * Creates a ConnectTimeoutException with the specified detail message. + * + * @param message The exception detail message + */ + public ConnectTimeoutException(final String message) { + super(message); + } + +} diff --git a/src/org/apache/http/conn/ConnectionKeepAliveStrategy.java b/src/org/apache/http/conn/ConnectionKeepAliveStrategy.java new file mode 100644 index 0000000..27d56cb --- /dev/null +++ b/src/org/apache/http/conn/ConnectionKeepAliveStrategy.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.conn; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpResponse; +import org.apache.http.protocol.HttpContext; + +/** + * Interface for deciding how long a connection can remain + * idle before being reused. + * + * @author <a href="mailto:sberlin at gmail.com">Sam Berlin</a> + * + * + * @version $Revision: $ + * + * @since 4.0 + */ +public interface ConnectionKeepAliveStrategy { + + /** + * Returns the duration of time which this connection can be safely kept + * idle. If the connection is left idle for longer than this period of time, + * it MUST not reused. A value of 0 or less may be returned to indicate that + * there is no suitable suggestion. + * + * When coupled with a {@link ConnectionReuseStrategy}, if + * {@link ConnectionReuseStrategy#keepAlive(HttpResponse, HttpContext) + * returns true, this allows you to control how long the reuse will last. If + * keepAlive returns false, this should have no meaningful impact + * + * @param response + * The last response received over the connection. + * @param context + * the context in which the connection is being used. + * + * @return the duration in ms for which it is safe to keep the connection + * idle, or <=0 if no suggested duration. + */ + long getKeepAliveDuration(HttpResponse response, HttpContext context); + +} diff --git a/src/org/apache/http/conn/ConnectionPoolTimeoutException.java b/src/org/apache/http/conn/ConnectionPoolTimeoutException.java new file mode 100644 index 0000000..7d4985e --- /dev/null +++ b/src/org/apache/http/conn/ConnectionPoolTimeoutException.java @@ -0,0 +1,62 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ConnectionPoolTimeoutException.java $ + * $Revision: 505684 $ + * $Date: 2007-02-10 04:40:02 -0800 (Sat, 10 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +/** + * A timeout while waiting for an available connection + * from a connection manager. + * + * @author <a href="mailto:laura@lwerner.org">Laura Werner</a> + * + * @since 4.0 + */ +public class ConnectionPoolTimeoutException extends ConnectTimeoutException { + + private static final long serialVersionUID = -7898874842020245128L; + + /** + * Creates a ConnectTimeoutException with a <tt>null</tt> detail message. + */ + public ConnectionPoolTimeoutException() { + super(); + } + + /** + * Creates a ConnectTimeoutException with the specified detail message. + * + * @param message The exception detail message + */ + public ConnectionPoolTimeoutException(String message) { + super(message); + } + +} diff --git a/src/org/apache/http/conn/ConnectionReleaseTrigger.java b/src/org/apache/http/conn/ConnectionReleaseTrigger.java new file mode 100644 index 0000000..a9ac12e --- /dev/null +++ b/src/org/apache/http/conn/ConnectionReleaseTrigger.java @@ -0,0 +1,86 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ConnectionReleaseTrigger.java $ + * $Revision: 672367 $ + * $Date: 2008-06-27 12:49:20 -0700 (Fri, 27 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.IOException; + + +/** + * Interface for releasing a connection. + * This can be implemented by various "trigger" objects which are + * associated with a connection, for example + * a {@link EofSensorInputStream stream} + * or an {@link BasicManagedEntity entity} + * or the {@link ManagedClientConnection connection} itself. + * <br/> + * The methods in this interface can safely be called multiple times. + * The first invocation releases the connection, subsequent calls + * are ignored. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672367 $ + * + * @since 4.0 + */ +public interface ConnectionReleaseTrigger { + + /** + * Releases the connection with the option of keep-alive. This is a + * "graceful" release and may cause IO operations for consuming the + * remainder of a response entity. Use + * {@link #abortConnection abortConnection} for a hard release. The + * connection may be reused as specified by the duration. + * + * @throws IOException + * in case of an IO problem. The connection will be released + * anyway. + */ + void releaseConnection() + throws IOException + ; + + /** + * Releases the connection without the option of keep-alive. + * This is a "hard" release that implies a shutdown of the connection. + * Use {@link #releaseConnection releaseConnection} for a graceful release. + * + * @throws IOException in case of an IO problem. + * The connection will be released anyway. + */ + void abortConnection() + throws IOException + ; + + +} // interface ConnectionReleaseTrigger diff --git a/src/org/apache/http/conn/EofSensorInputStream.java b/src/org/apache/http/conn/EofSensorInputStream.java new file mode 100644 index 0000000..0e1b90e --- /dev/null +++ b/src/org/apache/http/conn/EofSensorInputStream.java @@ -0,0 +1,326 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/EofSensorInputStream.java $ + * $Revision: 672367 $ + * $Date: 2008-06-27 12:49:20 -0700 (Fri, 27 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.InputStream; +import java.io.IOException; + + +/** + * A stream wrapper that triggers actions on {@link #close close()} and EOF. + * Primarily used to auto-release an underlying + * {@link ManagedClientConnection connection} + * when the response body is consumed or no longer needed. + * + * <p> + * This class is based on <code>AutoCloseInputStream</code> in HttpClient 3.1, + * but has notable differences. It does not allow mark/reset, distinguishes + * different kinds of event, and does not always close the underlying stream + * on EOF. That decision is left to the {@link EofSensorWatcher watcher}. + * </p> + * + * @see EofSensorWatcher EofSensorWatcher + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author Ortwin Glueck + * @author Eric Johnson + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672367 $ + * + * @since 4.0 + */ +// don't use FilterInputStream as the base class, we'd have to +// override markSupported(), mark(), and reset() to disable them +public class EofSensorInputStream extends InputStream + implements ConnectionReleaseTrigger { + + /** + * The wrapped input stream, while accessible. + * The value changes to <code>null</code> when the wrapped stream + * becomes inaccessible. + */ + protected InputStream wrappedStream; + + + /** + * Indicates whether this stream itself is closed. + * If it isn't, but {@link #wrappedStream wrappedStream} + * is <code>null</code>, we're running in EOF mode. + * All read operations will indicate EOF without accessing + * the underlying stream. After closing this stream, read + * operations will trigger an {@link IOException IOException}. + * + * @see #isReadAllowed isReadAllowed + */ + private boolean selfClosed; + + /** The watcher to be notified, if any. */ + private EofSensorWatcher eofWatcher; + + + /** + * Creates a new EOF sensor. + * If no watcher is passed, the underlying stream will simply be + * closed when EOF is detected or {@link #close close} is called. + * Otherwise, the watcher decides whether the underlying stream + * should be closed before detaching from it. + * + * @param in the wrapped stream + * @param watcher the watcher for events, or <code>null</code> for + * auto-close behavior without notification + */ + public EofSensorInputStream(final InputStream in, + final EofSensorWatcher watcher) { + if (in == null) { + throw new IllegalArgumentException + ("Wrapped stream may not be null."); + } + + wrappedStream = in; + selfClosed = false; + eofWatcher = watcher; + } + + + /** + * Checks whether the underlying stream can be read from. + * + * @return <code>true</code> if the underlying stream is accessible, + * <code>false</code> if this stream is in EOF mode and + * detached from the underlying stream + * + * @throws IOException if this stream is already closed + */ + protected boolean isReadAllowed() throws IOException { + if (selfClosed) { + throw new IOException("Attempted read on closed stream."); + } + return (wrappedStream != null); + } + + + // non-javadoc, see base class InputStream + @Override + public int read() throws IOException { + int l = -1; + + if (isReadAllowed()) { + try { + l = wrappedStream.read(); + checkEOF(l); + } catch (IOException ex) { + checkAbort(); + throw ex; + } + } + + return l; + } + + + // non-javadoc, see base class InputStream + @Override + public int read(byte[] b, int off, int len) throws IOException { + int l = -1; + + if (isReadAllowed()) { + try { + l = wrappedStream.read(b, off, len); + checkEOF(l); + } catch (IOException ex) { + checkAbort(); + throw ex; + } + } + + return l; + } + + + // non-javadoc, see base class InputStream + @Override + public int read(byte[] b) throws IOException { + int l = -1; + + if (isReadAllowed()) { + try { + l = wrappedStream.read(b); + checkEOF(l); + } catch (IOException ex) { + checkAbort(); + throw ex; + } + } + return l; + } + + + // non-javadoc, see base class InputStream + @Override + public int available() throws IOException { + int a = 0; // not -1 + + if (isReadAllowed()) { + try { + a = wrappedStream.available(); + // no checkEOF() here, available() can't trigger EOF + } catch (IOException ex) { + checkAbort(); + throw ex; + } + } + + return a; + } + + + // non-javadoc, see base class InputStream + @Override + public void close() throws IOException { + // tolerate multiple calls to close() + selfClosed = true; + checkClose(); + } + + + /** + * Detects EOF and notifies the watcher. + * This method should only be called while the underlying stream is + * still accessible. Use {@link #isReadAllowed isReadAllowed} to + * check that condition. + * <br/> + * If EOF is detected, the watcher will be notified and this stream + * is detached from the underlying stream. This prevents multiple + * notifications from this stream. + * + * @param eof the result of the calling read operation. + * A negative value indicates that EOF is reached. + * + * @throws IOException + * in case of an IO problem on closing the underlying stream + */ + protected void checkEOF(int eof) throws IOException { + + if ((wrappedStream != null) && (eof < 0)) { + try { + boolean scws = true; // should close wrapped stream? + if (eofWatcher != null) + scws = eofWatcher.eofDetected(wrappedStream); + if (scws) + wrappedStream.close(); + } finally { + wrappedStream = null; + } + } + } + + + /** + * Detects stream close and notifies the watcher. + * There's not much to detect since this is called by {@link #close close}. + * The watcher will only be notified if this stream is closed + * for the first time and before EOF has been detected. + * This stream will be detached from the underlying stream to prevent + * multiple notifications to the watcher. + * + * @throws IOException + * in case of an IO problem on closing the underlying stream + */ + protected void checkClose() throws IOException { + + if (wrappedStream != null) { + try { + boolean scws = true; // should close wrapped stream? + if (eofWatcher != null) + scws = eofWatcher.streamClosed(wrappedStream); + if (scws) + wrappedStream.close(); + } finally { + wrappedStream = null; + } + } + } + + + /** + * Detects stream abort and notifies the watcher. + * There's not much to detect since this is called by + * {@link #abortConnection abortConnection}. + * The watcher will only be notified if this stream is aborted + * for the first time and before EOF has been detected or the + * stream has been {@link #close closed} gracefully. + * This stream will be detached from the underlying stream to prevent + * multiple notifications to the watcher. + * + * @throws IOException + * in case of an IO problem on closing the underlying stream + */ + protected void checkAbort() throws IOException { + + if (wrappedStream != null) { + try { + boolean scws = true; // should close wrapped stream? + if (eofWatcher != null) + scws = eofWatcher.streamAbort(wrappedStream); + if (scws) + wrappedStream.close(); + } finally { + wrappedStream = null; + } + } + } + + + /** + * Same as {@link #close close()}. + */ + public void releaseConnection() throws IOException { + this.close(); + } + + /** + * Aborts this stream. + * This is a special version of {@link #close close()} which prevents + * re-use of the underlying connection, if any. Calling this method + * indicates that there should be no attempt to read until the end of + * the stream. + */ + public void abortConnection() throws IOException { + // tolerate multiple calls + selfClosed = true; + checkAbort(); + } + +} // class EOFSensorInputStream + diff --git a/src/org/apache/http/conn/EofSensorWatcher.java b/src/org/apache/http/conn/EofSensorWatcher.java new file mode 100644 index 0000000..837f8d9 --- /dev/null +++ b/src/org/apache/http/conn/EofSensorWatcher.java @@ -0,0 +1,112 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/EofSensorWatcher.java $ + * $Revision: 552264 $ + * $Date: 2007-07-01 02:37:47 -0700 (Sun, 01 Jul 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.InputStream; +import java.io.IOException; + + +/** + * A watcher for {@link EofSensorInputStream EofSensorInputStream}. + * Each stream will notify it's watcher at most once. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 552264 $ + * + * @since 4.0 + */ +public interface EofSensorWatcher { + + /** + * Indicates that EOF is detected. + * + * @param wrapped the underlying stream which has reached EOF + * + * @return <code>true</code> if <code>wrapped</code> should be closed, + * <code>false</code> if it should be left alone + * + * @throws IOException + * in case of an IO problem, for example if the watcher itself + * closes the underlying stream. The caller will leave the + * wrapped stream alone, as if <code>false</code> was returned. + */ + boolean eofDetected(InputStream wrapped) + throws IOException + ; + + + /** + * Indicates that the {@link EofSensorInputStream stream} is closed. + * This method will be called only if EOF was <i>not</i> detected + * before closing. Otherwise, {@link #eofDetected eofDetected} is called. + * + * @param wrapped the underlying stream which has not reached EOF + * + * @return <code>true</code> if <code>wrapped</code> should be closed, + * <code>false</code> if it should be left alone + * + * @throws IOException + * in case of an IO problem, for example if the watcher itself + * closes the underlying stream. The caller will leave the + * wrapped stream alone, as if <code>false</code> was returned. + */ + boolean streamClosed(InputStream wrapped) + throws IOException + ; + + + /** + * Indicates that the {@link EofSensorInputStream stream} is aborted. + * This method will be called only if EOF was <i>not</i> detected + * before aborting. Otherwise, {@link #eofDetected eofDetected} is called. + * <p/> + * This method will also be invoked when an input operation causes an + * IOException to be thrown to make sure the input stream gets shut down. + * + * @param wrapped the underlying stream which has not reached EOF + * + * @return <code>true</code> if <code>wrapped</code> should be closed, + * <code>false</code> if it should be left alone + * + * @throws IOException + * in case of an IO problem, for example if the watcher itself + * closes the underlying stream. The caller will leave the + * wrapped stream alone, as if <code>false</code> was returned. + */ + boolean streamAbort(InputStream wrapped) + throws IOException + ; + + +} // interface EofSensorWatcher diff --git a/src/org/apache/http/conn/HttpHostConnectException.java b/src/org/apache/http/conn/HttpHostConnectException.java new file mode 100644 index 0000000..743ca77 --- /dev/null +++ b/src/org/apache/http/conn/HttpHostConnectException.java @@ -0,0 +1,57 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/HttpHostConnectException.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.net.ConnectException; + +import org.apache.http.HttpHost; + +/** + * A {@link ConnectException} that specifies the {@link HttpHost} that was + * being connected to. + */ +public class HttpHostConnectException extends ConnectException { + + private static final long serialVersionUID = -3194482710275220224L; + + private final HttpHost host; + + public HttpHostConnectException(final HttpHost host, final ConnectException cause) { + super("Connection to " + host + " refused"); + this.host = host; + initCause(cause); + } + + public HttpHost getHost() { + return this.host; + } + +} diff --git a/src/org/apache/http/conn/ManagedClientConnection.java b/src/org/apache/http/conn/ManagedClientConnection.java new file mode 100644 index 0000000..f642cb9 --- /dev/null +++ b/src/org/apache/http/conn/ManagedClientConnection.java @@ -0,0 +1,261 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ManagedClientConnection.java $ + * $Revision: 672969 $ + * $Date: 2008-06-30 18:09:50 -0700 (Mon, 30 Jun 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLSession; + +import org.apache.http.HttpClientConnection; +import org.apache.http.HttpInetConnection; +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.routing.HttpRoute; + + + +/** + * A client-side connection with advanced connection logic. + * Instances are typically obtained from a connection manager. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672969 $ + * + * @since 4.0 + */ +public interface ManagedClientConnection extends + HttpClientConnection, HttpInetConnection, ConnectionReleaseTrigger { + + + /** + * Indicates whether this connection is secure. + * The return value is well-defined only while the connection is open. + * It may change even while the connection is open. + * + * @return <code>true</code> if this connection is secure, + * <code>false</code> otherwise + */ + boolean isSecure() + ; + + + /** + * Obtains the current route of this connection. + * + * @return the route established so far, or + * <code>null</code> if not connected + */ + HttpRoute getRoute() + ; + + + /** + * Obtains the SSL session of the underlying connection, if any. + * If this connection is open, and the underlying socket is an + * {@link javax.net.ssl.SSLSocket SSLSocket}, the SSL session of + * that socket is obtained. This is a potentially blocking operation. + * <br/> + * <b>Note:</b> Whether the underlying socket is an SSL socket + * can not necessarily be determined via {@link #isSecure}. + * Plain sockets may be considered secure, for example if they are + * connected to a known host in the same network segment. + * On the other hand, SSL sockets may be considered insecure, + * for example depending on the chosen cipher suite. + * + * @return the underlying SSL session if available, + * <code>null</code> otherwise + */ + SSLSession getSSLSession() + ; + + + /** + * Opens this connection according to the given route. + * + * @param route the route along which to open. It will be opened to + * the first proxy if present, or directly to the target. + * @param context the context for opening this connection + * @param params the parameters for opening this connection + * + * @throws IOException in case of a problem + */ + void open(HttpRoute route, HttpContext context, HttpParams params) + throws IOException + ; + + + /** + * Indicates that a tunnel to the target has been established. + * The route is the one previously passed to {@link #open open}. + * Subsequently, {@link #layerProtocol layerProtocol} can be called + * to layer the TLS/SSL protocol on top of the tunnelled connection. + * <br/> + * <b>Note:</b> In HttpClient 3, a call to the corresponding method + * would automatically trigger the layering of the TLS/SSL protocol. + * This is not the case anymore, you can establish a tunnel without + * layering a new protocol over the connection. + * + * @param secure <code>true</code> if the tunnel should be considered + * secure, <code>false</code> otherwise + * @param params the parameters for tunnelling this connection + * + * @throws IOException in case of a problem + */ + void tunnelTarget(boolean secure, HttpParams params) + throws IOException + ; + + + /** + * Indicates that a tunnel to an intermediate proxy has been established. + * This is used exclusively for so-called <i>proxy chains</i>, where + * a request has to pass through multiple proxies before reaching the + * target. In that case, all proxies but the last need to be tunnelled + * when establishing the connection. Tunnelling of the last proxy to the + * target is optional and would be indicated via {@link #tunnelTarget}. + * + * @param next the proxy to which the tunnel was established. + * This is <i>not</i> the proxy <i>through</i> which + * the tunnel was established, but the new end point + * of the tunnel. The tunnel does <i>not</i> yet + * reach to the target, use {@link #tunnelTarget} + * to indicate an end-to-end tunnel. + * @param secure <code>true</code> if the connection should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling this connection + * + * @throws IOException in case of a problem + */ + void tunnelProxy(HttpHost next, boolean secure, HttpParams params) + throws IOException + ; + + + /** + * Layers a new protocol on top of a {@link #tunnelTarget tunnelled} + * connection. This is typically used to create a TLS/SSL connection + * through a proxy. + * The route is the one previously passed to {@link #open open}. + * It is not guaranteed that the layered connection is + * {@link #isSecure secure}. + * + * @param context the context for layering on top of this connection + * @param params the parameters for layering on top of this connection + * + * @throws IOException in case of a problem + */ + void layerProtocol(HttpContext context, HttpParams params) + throws IOException + ; + + + /** + * Marks this connection as being in a reusable communication state. + * The checkpoints for reuseable communication states (in the absence + * of pipelining) are before sending a request and after receiving + * the response in it's entirety. + * The connection will automatically clear the checkpoint when + * used for communication. A call to this method indicates that + * the next checkpoint has been reached. + * <br/> + * A reusable communication state is necessary but not sufficient + * for the connection to be reused. + * A {@link #getRoute route} mismatch, the connection being closed, + * or other circumstances might prevent reuse. + */ + void markReusable() + ; + + + /** + * Marks this connection as not being in a reusable state. + * This can be used immediately before releasing this connection + * to prevent it's reuse. Reasons for preventing reuse include + * error conditions and the evaluation of a + * {@link org.apache.http.ConnectionReuseStrategy reuse strategy}. + * <br/> + * <b>Note:</b> + * It is <i>not</i> necessary to call here before writing to + * or reading from this connection. Communication attempts will + * automatically unmark the state as non-reusable. It can then + * be switched back using {@link #markReusable markReusable}. + */ + void unmarkReusable() + ; + + + /** + * Indicates whether this connection is in a reusable communication state. + * See {@link #markReusable markReusable} and + * {@link #unmarkReusable unmarkReusable} for details. + * + * @return <code>true</code> if this connection is marked as being in + * a reusable communication state, + * <code>false</code> otherwise + */ + boolean isMarkedReusable() + ; + + /** + * Assigns a state object to this connection. Connection managers may make + * use of the connection state when allocating persistent connections. + * + * @param state The state object + */ + void setState(Object state) + ; + + /** + * Returns the state object associated with this connection. + * + * @return The state object + */ + Object getState() + ; + + /** + * Sets the duration that this connection can remain idle before it is + * reused. The connection should not be used again if this time elapses. The + * idle duration must be reset after each request sent over this connection. + * The elapsed time starts counting when the connection is released, which + * is typically after the headers (and any response body, if present) is + * fully consumed. + */ + void setIdleDuration(long duration, TimeUnit unit); + +} // interface ManagedClientConnection diff --git a/src/org/apache/http/conn/MultihomePlainSocketFactory.java b/src/org/apache/http/conn/MultihomePlainSocketFactory.java new file mode 100644 index 0000000..e9549ab --- /dev/null +++ b/src/org/apache/http/conn/MultihomePlainSocketFactory.java @@ -0,0 +1,212 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/MultihomePlainSocketFactory.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Arrays; + +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.SocketFactory; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Socket factory that implements a simple multi-home fail-over on connect failure, + * provided the same hostname resolves to multiple {@link InetAddress}es. Please note + * the {@link #connectSocket(Socket, String, int, InetAddress, int, HttpParams)} + * method cannot be reliably interrupted by closing the socket returned by the + * {@link #createSocket()} method. + */ +public final class MultihomePlainSocketFactory implements SocketFactory { + + /** + * The factory singleton. + */ + private static final + MultihomePlainSocketFactory DEFAULT_FACTORY = new MultihomePlainSocketFactory(); + + /** + * Gets the singleton instance of this class. + * @return the one and only plain socket factory + */ + public static MultihomePlainSocketFactory getSocketFactory() { + return DEFAULT_FACTORY; + } + + /** + * Restricted default constructor. + */ + private MultihomePlainSocketFactory() { + super(); + } + + + // non-javadoc, see interface org.apache.http.conn.SocketFactory + public Socket createSocket() { + return new Socket(); + } + + /** + * Attempts to connects the socket to any of the {@link InetAddress}es the + * given host name resolves to. If connection to all addresses fail, the + * last I/O exception is propagated to the caller. + * + * @param sock socket to connect to any of the given addresses + * @param host Host name to connect to + * @param port the port to connect to + * @param localAddress local address + * @param localPort local port + * @param params HTTP parameters + * + * @throws IOException if an error occurs during the connection + * @throws SocketTimeoutException if timeout expires before connecting + */ + public Socket connectSocket(Socket sock, String host, int port, + InetAddress localAddress, int localPort, + HttpParams params) + throws IOException { + + if (host == null) { + throw new IllegalArgumentException("Target host may not be null."); + } + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null."); + } + + if (sock == null) + sock = createSocket(); + + if ((localAddress != null) || (localPort > 0)) { + + // we need to bind explicitly + if (localPort < 0) + localPort = 0; // indicates "any" + + InetSocketAddress isa = + new InetSocketAddress(localAddress, localPort); + sock.bind(isa); + } + + int timeout = HttpConnectionParams.getConnectionTimeout(params); + + InetAddress[] inetadrs = InetAddress.getAllByName(host); + List<InetAddress> addresses = new ArrayList<InetAddress>(inetadrs.length); + addresses.addAll(Arrays.asList(inetadrs)); + Collections.shuffle(addresses); + + IOException lastEx = null; + for (InetAddress address: addresses) { + try { + sock.connect(new InetSocketAddress(address, port), timeout); + break; + } catch (SocketTimeoutException ex) { + throw ex; + } catch (IOException ex) { + // create new socket + sock = new Socket(); + // keep the last exception and retry + lastEx = ex; + } + } + if (lastEx != null) { + throw lastEx; + } + return sock; + } // connectSocket + + + /** + * Checks whether a socket connection is secure. + * This factory creates plain socket connections + * which are not considered secure. + * + * @param sock the connected socket + * + * @return <code>false</code> + * + * @throws IllegalArgumentException if the argument is invalid + */ + public final boolean isSecure(Socket sock) + throws IllegalArgumentException { + + if (sock == null) { + throw new IllegalArgumentException("Socket may not be null."); + } + // This class check assumes that createSocket() calls the constructor + // directly. If it was using javax.net.SocketFactory, we couldn't make + // an assumption about the socket class here. + if (sock.getClass() != Socket.class) { + throw new IllegalArgumentException + ("Socket not created by this factory."); + } + // This check is performed last since it calls a method implemented + // by the argument object. getClass() is final in java.lang.Object. + if (sock.isClosed()) { + throw new IllegalArgumentException("Socket is closed."); + } + + return false; + + } // isSecure + + + /** + * Compares this factory with an object. + * There is only one instance of this class. + * + * @param obj the object to compare with + * + * @return iff the argument is this object + */ + @Override + public boolean equals(Object obj) { + return (obj == this); + } + + /** + * Obtains a hash code for this object. + * All instances of this class have the same hash code. + * There is only one instance of this class. + */ + @Override + public int hashCode() { + return PlainSocketFactory.class.hashCode(); + } + +} diff --git a/src/org/apache/http/conn/OperatedClientConnection.java b/src/org/apache/http/conn/OperatedClientConnection.java new file mode 100644 index 0000000..2eda8e9 --- /dev/null +++ b/src/org/apache/http/conn/OperatedClientConnection.java @@ -0,0 +1,174 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/OperatedClientConnection.java $ + * $Revision: 646087 $ + * $Date: 2008-04-08 14:36:46 -0700 (Tue, 08 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.http.HttpClientConnection; +import org.apache.http.HttpHost; +import org.apache.http.HttpInetConnection; +import org.apache.http.params.HttpParams; + + +/** + * A client-side connection that relies on outside logic to connect sockets to the + * appropriate hosts. It can be operated directly by an application, or through an + * {@link ClientConnectionOperator operator}. + * + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 646087 $ $Date: 2008-04-08 14:36:46 -0700 (Tue, 08 Apr 2008) $ + * + * @since 4.0 + */ +public interface OperatedClientConnection + extends HttpClientConnection, HttpInetConnection { + + /** + * Obtains the target host for this connection. + * If the connection is to a proxy but not tunnelled, this is + * the proxy. If the connection is tunnelled through a proxy, + * this is the target of the tunnel. + * <br/> + * The return value is well-defined only while the connection is open. + * It may change even while the connection is open, + * because of an {@link #update update}. + * + * @return the host to which this connection is opened + */ + HttpHost getTargetHost() + ; + + /** + * Indicates whether this connection is secure. + * The return value is well-defined only while the connection is open. + * It may change even while the connection is open, + * because of an {@link #update update}. + * + * @return <code>true</code> if this connection is secure, + * <code>false</code> otherwise + */ + boolean isSecure() + ; + + /** + * Obtains the socket for this connection. + * The return value is well-defined only while the connection is open. + * It may change even while the connection is open, + * because of an {@link #update update}. + * + * @return the socket for communicating with the + * {@link #getTargetHost target host} + */ + Socket getSocket() + ; + + + // There is no getParams(). For the moment, we + // do not require connections to store parameters. + + + /** + * Signals that this connection is in the process of being open. + * <br/> + * By calling this method, you can provide the connection with + * the unconnected socket that will be connected before + * {@link #openCompleted} is called. This allows + * the connection to close that socket if + * {@link org.apache.http.HttpConnection#shutdown shutdown} + * is called before it is open. Closing the unconnected socket + * will interrupt a thread that is blocked on the connect. + * Otherwise, that thread will either time out on the connect, + * or it returns successfully and then opens this connection + * which was just shut down. + * <br/> + * You also must call {@link #openCompleted} in order to complete + * the process + * + * @param sock the unconnected socket which is about to + * be connected. + * @param target the target host of this connection + */ + void opening(Socket sock, HttpHost target) + throws IOException + ; + + + /** + * Signals that the connection has been successfully open. + * An attempt to call this method on an open connection will cause + * an exception. + * + * @param secure <code>true</code> if this connection is secure, for + * example if an <code>SSLSocket</code> is used, or + * <code>false</code> if it is not secure + * @param params parameters for this connection. The parameters will + * be used when creating dependent objects, for example + * to determine buffer sizes. + */ + void openCompleted(boolean secure, HttpParams params) + throws IOException + ; + + + /** + * Updates this connection. + * A connection can be updated only while it is open. + * Updates are used for example when a tunnel has been established, + * or when a TLS/SSL connection has been layered on top of a plain + * socket connection. + * <br/> + * <b>Note:</b> Updating the connection will <i>not</i> close the + * previously used socket. It is the caller's responsibility to close + * that socket if it is no longer required. + * + * @param sock the new socket for communicating with the target host, + * or <code>null</code> to continue using the old socket. + * If <code>null</code> is passed, helper objects that + * depend on the socket should be re-used. In that case, + * some changes in the parameters will not take effect. + * @param target the new target host of this connection + * @param secure <code>true</code> if this connection is now secure, + * <code>false</code> if it is not secure + * @param params new parameters for this connection + */ + void update(Socket sock, HttpHost target, + boolean secure, HttpParams params) + throws IOException + ; + + +} // interface OperatedClientConnection diff --git a/src/org/apache/http/conn/package.html b/src/org/apache/http/conn/package.html new file mode 100644 index 0000000..e0b29cd --- /dev/null +++ b/src/org/apache/http/conn/package.html @@ -0,0 +1,91 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/package.html $ + * $Revision: 651813 $ + * $Date: 2008-04-26 03:43:34 -0700 (Sat, 26 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The client-side connection management and handling API +at the heart of what is referred to as <i>HttpConn</i>. +This component provides interfaces and implementations for +opening and managing connections. + +<p> +The lowest layer of connection handling is comprised of +{@link org.apache.http.conn.OperatedClientConnection OperatedClientConnection} +and +{@link org.apache.http.conn.ClientConnectionOperator ClientConnectionOperator}. +The connection interface extends the core +{@link org.apache.http.HttpClientConnection HttpClientConnection} +by operations to set and update a socket. +An operator encapsulates the logic to open and layer sockets, +typically using a {@link org.apache.http.conn.scheme.SocketFactory SocketFactory}. +The socket factory for a protocol +{@link org.apache.http.conn.scheme.Scheme Scheme} +such as "http" or "https" can be looked up in a +{@link org.apache.http.conn.scheme.SchemeRegistry SchemeRegistry}. +Applications without a need for sophisticated connection management +can use this layer directly. +</p> + +<p> +On top of that lies the connection management layer. A +{@link org.apache.http.conn.ClientConnectionManager ClientConnectionManager} +internally manages operated connections, but hands out instances of +{@link org.apache.http.conn.ManagedClientConnection ManagedClientConnection}. +This interface abstracts from the underlying socket operations and +provides convenient methods for opening and updating sockets in order +to establish a {@link org.apache.http.conn.routing.HttpRoute route}. +The operator is encapsulated by the connection manager and called +automatically. + +<br/> + +Connections obtained from a manager have to be returned after use. +This can be {@link org.apache.http.conn.ConnectionReleaseTrigger triggered} +on various levels, either by releasing the +{@link org.apache.http.conn.ManagedClientConnection + connection} +directly, or by calling a method on an +{@link org.apache.http.conn.BasicManagedEntity entity} +received from the connection, or by closing the +{@link org.apache.http.conn.EofSensorInputStream stream} +from which that entity is being read. + +Connection managers will try to keep returned connections alive in +order to re-use them for subsequent requests along the same route. +The managed connection interface and all triggers for connection release +provide methods to enable or disable this behavior. +</p> + +</body> +</html> diff --git a/src/org/apache/http/conn/params/ConnConnectionPNames.java b/src/org/apache/http/conn/params/ConnConnectionPNames.java new file mode 100644 index 0000000..ff1a090 --- /dev/null +++ b/src/org/apache/http/conn/params/ConnConnectionPNames.java @@ -0,0 +1,65 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnConnectionPNames.java $ + * $Revision: 576068 $ + * $Date: 2007-09-16 03:25:01 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + + +/** + * Parameter names for connections in HttpConn. + * + * @version $Revision: 576068 $ + * + * @since 4.0 + */ +public interface ConnConnectionPNames { + + + /** + * Defines the maximum number of ignorable lines before we expect + * a HTTP response's status line. + * <p> + * With HTTP/1.1 persistent connections, the problem arises that + * broken scripts could return a wrong Content-Length + * (there are more bytes sent than specified). + * Unfortunately, in some cases, this cannot be detected after the + * bad response, but only before the next one. + * So HttpClient must be able to skip those surplus lines this way. + * </p> + * <p> + * This parameter expects a value of type {@link Integer}. + * 0 disallows all garbage/empty lines before the status line. + * Use {@link java.lang.Integer#MAX_VALUE} for unlimited + * (default in lenient mode). + * </p> + */ + public static final String MAX_STATUS_LINE_GARBAGE = "http.connection.max-status-line-garbage"; + + +} diff --git a/src/org/apache/http/conn/params/ConnConnectionParamBean.java b/src/org/apache/http/conn/params/ConnConnectionParamBean.java new file mode 100644 index 0000000..094b152 --- /dev/null +++ b/src/org/apache/http/conn/params/ConnConnectionParamBean.java @@ -0,0 +1,55 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnConnectionParamBean.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + +import org.apache.http.params.HttpAbstractParamBean; +import org.apache.http.params.HttpParams; + +/** + * Allows for setting parameters relating to connections on + * {@link HttpParams}. This class ensures that the values set on the params + * are type-safe. + */ +public class ConnConnectionParamBean extends HttpAbstractParamBean { + + public ConnConnectionParamBean (final HttpParams params) { + super(params); + } + + /** + * @see ConnConnectionPNames#MAX_STATUS_LINE_GARBAGE + */ + public void setMaxStatusLineGarbage (final int maxStatusLineGarbage) { + params.setIntParameter(ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, maxStatusLineGarbage); + } + +} diff --git a/src/org/apache/http/conn/params/ConnManagerPNames.java b/src/org/apache/http/conn/params/ConnManagerPNames.java new file mode 100644 index 0000000..1184b12 --- /dev/null +++ b/src/org/apache/http/conn/params/ConnManagerPNames.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnManagerPNames.java $ + * $Revision: 658781 $ + * $Date: 2008-05-21 10:42:13 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + + +/** + * Parameter names for connection managers in HttpConn. + * + * @version $Revision: 658781 $ + * + * @since 4.0 + */ +public interface ConnManagerPNames { + + /** + * Defines the timeout in milliseconds used when retrieving an instance of + * {@link org.apache.http.conn.ManagedClientConnection} from the + * {@link org.apache.http.conn.ClientConnectionManager}. + * <p> + * This parameter expects a value of type {@link Long}. + * </p> + */ + public static final String TIMEOUT = "http.conn-manager.timeout"; + + /** + * Defines the maximum number of connections per route. + * This limit is interpreted by client connection managers + * and applies to individual manager instances. + * <p> + * This parameter expects a value of type {@link ConnPerRoute}. + * </p> + */ + public static final String MAX_CONNECTIONS_PER_ROUTE = "http.conn-manager.max-per-route"; + + /** + * Defines the maximum number of connections in total. + * This limit is interpreted by client connection managers + * and applies to individual manager instances. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + */ + public static final String MAX_TOTAL_CONNECTIONS = "http.conn-manager.max-total"; + +} diff --git a/src/org/apache/http/conn/params/ConnManagerParamBean.java b/src/org/apache/http/conn/params/ConnManagerParamBean.java new file mode 100644 index 0000000..830b7bf --- /dev/null +++ b/src/org/apache/http/conn/params/ConnManagerParamBean.java @@ -0,0 +1,62 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnManagerParamBean.java $ + * $Revision: 658781 $ + * $Date: 2008-05-21 10:42:13 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + +import org.apache.http.params.HttpAbstractParamBean; +import org.apache.http.params.HttpParams; + +/** + * Allows for setting parameters relating to connection managers on + * {@link HttpParams}. This class ensures that the values set on the params + * are type-safe. + */ +public class ConnManagerParamBean extends HttpAbstractParamBean { + + public ConnManagerParamBean (final HttpParams params) { + super(params); + } + + public void setTimeout (final long timeout) { + params.setLongParameter(ConnManagerPNames.TIMEOUT, timeout); + } + + /** @see ConnManagerPNames#MAX_TOTAL_CONNECTIONS */ + public void setMaxTotalConnections (final int maxConnections) { + params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, maxConnections); + } + + /** @see ConnManagerPNames#MAX_CONNECTIONS_PER_ROUTE */ + public void setConnectionsPerRoute(final ConnPerRouteBean connPerRoute) { + params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, connPerRoute); + } + +} diff --git a/src/org/apache/http/conn/params/ConnManagerParams.java b/src/org/apache/http/conn/params/ConnManagerParams.java new file mode 100644 index 0000000..c6e042e --- /dev/null +++ b/src/org/apache/http/conn/params/ConnManagerParams.java @@ -0,0 +1,169 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnManagerParams.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.params.HttpParams; + +/** + * This class represents a collection of HTTP protocol parameters applicable + * to client-side + * {@link org.apache.http.conn.ClientConnectionManager connection managers}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author Michael Becke + * + * @version $Revision: 658785 $ + * + * @since 4.0 + * + * @see ConnManagerPNames + */ +public final class ConnManagerParams implements ConnManagerPNames { + + /** The default maximum number of connections allowed overall */ + public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; + + /** + * Returns the timeout in milliseconds used when retrieving a + * {@link org.apache.http.conn.ManagedClientConnection} from the + * {@link org.apache.http.conn.ClientConnectionManager}. + * + * @return timeout in milliseconds. + */ + public static long getTimeout(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getLongParameter(TIMEOUT, 0); + } + + /** + * Sets the timeout in milliseconds used when retrieving a + * {@link org.apache.http.conn.ManagedClientConnection} from the + * {@link org.apache.http.conn.ClientConnectionManager}. + * + * @param timeout the timeout in milliseconds + */ + public static void setTimeout(final HttpParams params, long timeout) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setLongParameter(TIMEOUT, timeout); + } + + /** The default maximum number of connections allowed per host */ + private static final ConnPerRoute DEFAULT_CONN_PER_ROUTE = new ConnPerRoute() { + + public int getMaxForRoute(HttpRoute route) { + return ConnPerRouteBean.DEFAULT_MAX_CONNECTIONS_PER_ROUTE; + } + + }; + + /** + * Sets lookup interface for maximum number of connections allowed per route. + * + * @param params HTTP parameters + * @param connPerRoute lookup interface for maximum number of connections allowed + * per route + * + * @see ConnManagerPNames#MAX_CONNECTIONS_PER_ROUTE + */ + public static void setMaxConnectionsPerRoute(final HttpParams params, + final ConnPerRoute connPerRoute) { + if (params == null) { + throw new IllegalArgumentException + ("HTTP parameters must not be null."); + } + params.setParameter(MAX_CONNECTIONS_PER_ROUTE, connPerRoute); + } + + /** + * Returns lookup interface for maximum number of connections allowed per route. + * + * @param params HTTP parameters + * + * @return lookup interface for maximum number of connections allowed per route. + * + * @see ConnManagerPNames#MAX_CONNECTIONS_PER_ROUTE + */ + public static ConnPerRoute getMaxConnectionsPerRoute(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException + ("HTTP parameters must not be null."); + } + ConnPerRoute connPerRoute = (ConnPerRoute) params.getParameter(MAX_CONNECTIONS_PER_ROUTE); + if (connPerRoute == null) { + connPerRoute = DEFAULT_CONN_PER_ROUTE; + } + return connPerRoute; + } + + + /** + * Sets the maximum number of connections allowed. + * + * @param params HTTP parameters + * @param maxTotalConnections The maximum number of connections allowed. + * + * @see ConnManagerPNames#MAX_TOTAL_CONNECTIONS + */ + public static void setMaxTotalConnections( + final HttpParams params, + int maxTotalConnections) { + if (params == null) { + throw new IllegalArgumentException + ("HTTP parameters must not be null."); + } + params.setIntParameter(MAX_TOTAL_CONNECTIONS, maxTotalConnections); + } + + /** + * Gets the maximum number of connections allowed. + * + * @param params HTTP parameters + * + * @return The maximum number of connections allowed. + * + * @see ConnManagerPNames#MAX_TOTAL_CONNECTIONS + */ + public static int getMaxTotalConnections( + final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException + ("HTTP parameters must not be null."); + } + return params.getIntParameter(MAX_TOTAL_CONNECTIONS, DEFAULT_MAX_TOTAL_CONNECTIONS); + } + + +} diff --git a/src/org/apache/http/conn/params/ConnPerRoute.java b/src/org/apache/http/conn/params/ConnPerRoute.java new file mode 100644 index 0000000..abff04e --- /dev/null +++ b/src/org/apache/http/conn/params/ConnPerRoute.java @@ -0,0 +1,51 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnPerRoute.java $ + * $Revision: 651813 $ + * $Date: 2008-04-26 03:43:34 -0700 (Sat, 26 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + +import org.apache.http.conn.routing.HttpRoute; + +/** + * This interface is intended for looking up maximum number of connections + * allowed for for a given route. This class can be used by pooling + * {@link org.apache.http.conn.ClientConnectionManager connection managers} for + * a fine-grained control of connections on a per route basis. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 651813 $ + * + * @since 4.0 + */ +public interface ConnPerRoute { + + int getMaxForRoute(HttpRoute route); + +} diff --git a/src/org/apache/http/conn/params/ConnPerRouteBean.java b/src/org/apache/http/conn/params/ConnPerRouteBean.java new file mode 100644 index 0000000..c6a36e3 --- /dev/null +++ b/src/org/apache/http/conn/params/ConnPerRouteBean.java @@ -0,0 +1,114 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnPerRouteBean.java $ + * $Revision: 652947 $ + * $Date: 2008-05-02 16:15:40 -0700 (Fri, 02 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.conn.routing.HttpRoute; + +/** + * This class maintains a map of HTTP routes to maximum number of connections allowed + * for those routes. This class can be used by pooling + * {@link org.apache.http.conn.ClientConnectionManager connection managers} for + * a fine-grained control of connections on a per route basis. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 652947 $ + * + * @since 4.0 + */ +public final class ConnPerRouteBean implements ConnPerRoute { + + /** The default maximum number of connections allowed per host */ + public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 2; // Per RFC 2616 sec 8.1.4 + + private final Map<HttpRoute, Integer> maxPerHostMap; + + private int defaultMax; + + public ConnPerRouteBean(int defaultMax) { + super(); + this.maxPerHostMap = new HashMap<HttpRoute, Integer>(); + setDefaultMaxPerRoute(defaultMax); + } + + public ConnPerRouteBean() { + this(DEFAULT_MAX_CONNECTIONS_PER_ROUTE); + } + + public int getDefaultMax() { + return this.defaultMax; + } + + public void setDefaultMaxPerRoute(int max) { + if (max < 1) { + throw new IllegalArgumentException + ("The maximum must be greater than 0."); + } + this.defaultMax = max; + } + + public void setMaxForRoute(final HttpRoute route, int max) { + if (route == null) { + throw new IllegalArgumentException + ("HTTP route may not be null."); + } + if (max < 1) { + throw new IllegalArgumentException + ("The maximum must be greater than 0."); + } + this.maxPerHostMap.put(route, Integer.valueOf(max)); + } + + public int getMaxForRoute(final HttpRoute route) { + if (route == null) { + throw new IllegalArgumentException + ("HTTP route may not be null."); + } + Integer max = this.maxPerHostMap.get(route); + if (max != null) { + return max.intValue(); + } else { + return this.defaultMax; + } + } + + public void setMaxForRoutes(final Map<HttpRoute, Integer> map) { + if (map == null) { + return; + } + this.maxPerHostMap.clear(); + this.maxPerHostMap.putAll(map); + } + +} diff --git a/src/org/apache/http/conn/params/ConnRoutePNames.java b/src/org/apache/http/conn/params/ConnRoutePNames.java new file mode 100644 index 0000000..f9d42db --- /dev/null +++ b/src/org/apache/http/conn/params/ConnRoutePNames.java @@ -0,0 +1,84 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnRoutePNames.java $ + * $Revision: 613656 $ + * $Date: 2008-01-20 11:06:56 -0800 (Sun, 20 Jan 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + +/** + * Parameter names for routing in HttpConn. + * + * @version $Revision: 613656 $ + * + * @since 4.0 + */ +public interface ConnRoutePNames { + + /** + * Parameter for the default proxy. + * The default value will be used by some + * {@link org.apache.http.conn.routing.HttpRoutePlanner HttpRoutePlanner} + * implementations, in particular the default implementation. + * <p> + * This parameter expects a value of type {@link org.apache.http.HttpHost}. + * </p> + */ + public static final String DEFAULT_PROXY = "http.route.default-proxy"; + + + /** + * Parameter for the local address. + * On machines with multiple network interfaces, this parameter + * can be used to select the network interface from which the + * connection originates. + * It will be interpreted by the standard + * {@link org.apache.http.conn.routing.HttpRoutePlanner HttpRoutePlanner} + * implementations, in particular the default implementation. + * <p> + * This parameter expects a value of type {@link java.net.InetAddress}. + * </p> + */ + public static final String LOCAL_ADDRESS = "http.route.local-address"; + + + /** + * Parameter for an forced route. + * The forced route will be interpreted by the standard + * {@link org.apache.http.conn.routing.HttpRoutePlanner HttpRoutePlanner} + * implementations. + * Instead of computing a route, the given forced route will be + * returned, even if it points to the wrong target host. + * <p> + * This parameter expects a value of type + * {@link org.apache.http.conn.routing.HttpRoute HttpRoute}. + * </p> + */ + public static final String FORCED_ROUTE = "http.route.forced-route"; + +} + diff --git a/src/org/apache/http/conn/params/ConnRouteParamBean.java b/src/org/apache/http/conn/params/ConnRouteParamBean.java new file mode 100644 index 0000000..9464c02 --- /dev/null +++ b/src/org/apache/http/conn/params/ConnRouteParamBean.java @@ -0,0 +1,67 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnRouteParamBean.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + +import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.params.HttpAbstractParamBean; +import org.apache.http.params.HttpParams; + +/** + * Allows for setting parameters relating to connection routes on + * {@link HttpParams}. This class ensures that the values set on the params + * are type-safe. + */ +public class ConnRouteParamBean extends HttpAbstractParamBean { + + public ConnRouteParamBean (final HttpParams params) { + super(params); + } + + /** @see ConnRoutePNames#DEFAULT_PROXY */ + public void setDefaultProxy (final HttpHost defaultProxy) { + params.setParameter(ConnRoutePNames.DEFAULT_PROXY, defaultProxy); + } + + /** @see ConnRoutePNames#LOCAL_ADDRESS */ + public void setLocalAddress (final InetAddress address) { + params.setParameter(ConnRoutePNames.LOCAL_ADDRESS, address); + } + + /** @see ConnRoutePNames#FORCED_ROUTE */ + public void setForcedRoute (final HttpRoute route) { + params.setParameter(ConnRoutePNames.FORCED_ROUTE, route); + } + +} diff --git a/src/org/apache/http/conn/params/ConnRouteParams.java b/src/org/apache/http/conn/params/ConnRouteParams.java new file mode 100644 index 0000000..2fa1654 --- /dev/null +++ b/src/org/apache/http/conn/params/ConnRouteParams.java @@ -0,0 +1,203 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/ConnRouteParams.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.params; + + +import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.conn.routing.HttpRoute; + + + +/** + * An adaptor for accessing route related parameters in {@link HttpParams}. + * See {@link ConnRoutePNames} for parameter name definitions. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 658785 $ + * + * @since 4.0 + */ +public class ConnRouteParams implements ConnRoutePNames { + + /** + * A special value indicating "no host". + * This relies on a nonsense scheme name to avoid conflicts + * with actual hosts. Note that this is a <i>valid</i> host. + */ + public static final HttpHost NO_HOST = + new HttpHost("127.0.0.255", 0, "no-host"); + + /** + * A special value indicating "no route". + * This is a route with {@link #NO_HOST} as the target. + */ + public static final HttpRoute NO_ROUTE = new HttpRoute(NO_HOST); + + + /** Disabled default constructor. */ + private ConnRouteParams() { + // no body + } + + + /** + * Obtains the {@link ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY} + * parameter value. + * {@link #NO_HOST} will be mapped to <code>null</code>, + * to allow unsetting in a hierarchy. + * + * @param params the parameters in which to look up + * + * @return the default proxy set in the argument parameters, or + * <code>null</code> if not set + */ + public static HttpHost getDefaultProxy(HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("Parameters must not be null."); + } + HttpHost proxy = (HttpHost) + params.getParameter(DEFAULT_PROXY); + if ((proxy != null) && NO_HOST.equals(proxy)) { + // value is explicitly unset + proxy = null; + } + return proxy; + } + + + /** + * Sets the {@link ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY} + * parameter value. + * + * @param params the parameters in which to set the value + * @param proxy the value to set, may be <code>null</code>. + * Note that {@link #NO_HOST} will be mapped to + * <code>null</code> by {@link #getDefaultProxy}, + * to allow for explicit unsetting in hierarchies. + */ + public static void setDefaultProxy(HttpParams params, + HttpHost proxy) { + if (params == null) { + throw new IllegalArgumentException("Parameters must not be null."); + } + params.setParameter(DEFAULT_PROXY, proxy); + } + + + /** + * Obtains the {@link ConnRoutePNames#FORCED_ROUTE FORCED_ROUTE} + * parameter value. + * {@link #NO_ROUTE} will be mapped to <code>null</code>, + * to allow unsetting in a hierarchy. + * + * @param params the parameters in which to look up + * + * @return the forced route set in the argument parameters, or + * <code>null</code> if not set + */ + public static HttpRoute getForcedRoute(HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("Parameters must not be null."); + } + HttpRoute route = (HttpRoute) + params.getParameter(FORCED_ROUTE); + if ((route != null) && NO_ROUTE.equals(route)) { + // value is explicitly unset + route = null; + } + return route; + } + + + /** + * Sets the {@link ConnRoutePNames#FORCED_ROUTE FORCED_ROUTE} + * parameter value. + * + * @param params the parameters in which to set the value + * @param route the value to set, may be <code>null</code>. + * Note that {@link #NO_ROUTE} will be mapped to + * <code>null</code> by {@link #getForcedRoute}, + * to allow for explicit unsetting in hierarchies. + */ + public static void setForcedRoute(HttpParams params, + HttpRoute route) { + if (params == null) { + throw new IllegalArgumentException("Parameters must not be null."); + } + params.setParameter(FORCED_ROUTE, route); + } + + + /** + * Obtains the {@link ConnRoutePNames#LOCAL_ADDRESS LOCAL_ADDRESS} + * parameter value. + * There is no special value that would automatically be mapped to + * <code>null</code>. You can use the wildcard address (0.0.0.0 for IPv4, + * :: for IPv6) to override a specific local address in a hierarchy. + * + * @param params the parameters in which to look up + * + * @return the local address set in the argument parameters, or + * <code>null</code> if not set + */ + public static InetAddress getLocalAddress(HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("Parameters must not be null."); + } + InetAddress local = (InetAddress) + params.getParameter(LOCAL_ADDRESS); + // no explicit unsetting + return local; + } + + + /** + * Sets the {@link ConnRoutePNames#LOCAL_ADDRESS LOCAL_ADDRESS} + * parameter value. + * + * @param params the parameters in which to set the value + * @param local the value to set, may be <code>null</code> + */ + public static void setLocalAddress(HttpParams params, + InetAddress local) { + if (params == null) { + throw new IllegalArgumentException("Parameters must not be null."); + } + params.setParameter(LOCAL_ADDRESS, local); + } + +} + diff --git a/src/org/apache/http/conn/params/package.html b/src/org/apache/http/conn/params/package.html new file mode 100644 index 0000000..9b80420 --- /dev/null +++ b/src/org/apache/http/conn/params/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/params/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Parameters for configuring <i>HttpConn</i>. + +</body> +</html> diff --git a/src/org/apache/http/conn/routing/BasicRouteDirector.java b/src/org/apache/http/conn/routing/BasicRouteDirector.java new file mode 100644 index 0000000..a3714ec --- /dev/null +++ b/src/org/apache/http/conn/routing/BasicRouteDirector.java @@ -0,0 +1,181 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/BasicRouteDirector.java $ + * $Revision: 620255 $ + * $Date: 2008-02-10 02:23:55 -0800 (Sun, 10 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.routing; + + + +/** + * Basic implementation of an {@link HttpRouteDirector HttpRouteDirector}. + * This implementation is stateless and therefore thread-safe. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 620255 $ + * + * @since 4.0 + */ +public class BasicRouteDirector implements HttpRouteDirector { + + // public default constructor + + + /** + * Provides the next step. + * + * @param plan the planned route + * @param fact the currently established route, or + * <code>null</code> if nothing is established + * + * @return one of the constants defined in this class, indicating + * either the next step to perform, or success, or failure. + * 0 is for success, a negative value for failure. + */ + public int nextStep(RouteInfo plan, RouteInfo fact) { + if (plan == null) { + throw new IllegalArgumentException + ("Planned route may not be null."); + } + + int step = UNREACHABLE; + + if ((fact == null) || (fact.getHopCount() < 1)) + step = firstStep(plan); + else if (plan.getHopCount() > 1) + step = proxiedStep(plan, fact); + else + step = directStep(plan, fact); + + return step; + + } // nextStep + + + /** + * Determines the first step to establish a route. + * + * @param plan the planned route + * + * @return the first step + */ + protected int firstStep(RouteInfo plan) { + + return (plan.getHopCount() > 1) ? + CONNECT_PROXY : CONNECT_TARGET; + } + + + /** + * Determines the next step to establish a direct connection. + * + * @param plan the planned route + * @param fact the currently established route + * + * @return one of the constants defined in this class, indicating + * either the next step to perform, or success, or failure + */ + protected int directStep(RouteInfo plan, RouteInfo fact) { + + if (fact.getHopCount() > 1) + return UNREACHABLE; + if (!plan.getTargetHost().equals(fact.getTargetHost())) + return UNREACHABLE; + // If the security is too low, we could now suggest to layer + // a secure protocol on the direct connection. Layering on direct + // connections has not been supported in HttpClient 3.x, we don't + // consider it here until there is a real-life use case for it. + + // Should we tolerate if security is better than planned? + // (plan.isSecure() && !fact.isSecure()) + if (plan.isSecure() != fact.isSecure()) + return UNREACHABLE; + + // Local address has to match only if the plan specifies one. + if ((plan.getLocalAddress() != null) && + !plan.getLocalAddress().equals(fact.getLocalAddress()) + ) + return UNREACHABLE; + + return COMPLETE; + } + + + /** + * Determines the next step to establish a connection via proxy. + * + * @param plan the planned route + * @param fact the currently established route + * + * @return one of the constants defined in this class, indicating + * either the next step to perform, or success, or failure + */ + protected int proxiedStep(RouteInfo plan, RouteInfo fact) { + + if (fact.getHopCount() <= 1) + return UNREACHABLE; + if (!plan.getTargetHost().equals(fact.getTargetHost())) + return UNREACHABLE; + final int phc = plan.getHopCount(); + final int fhc = fact.getHopCount(); + if (phc < fhc) + return UNREACHABLE; + + for (int i=0; i<fhc-1; i++) { + if (!plan.getHopTarget(i).equals(fact.getHopTarget(i))) + return UNREACHABLE; + } + // now we know that the target matches and proxies so far are the same + if (phc > fhc) + return TUNNEL_PROXY; // need to extend the proxy chain + + // proxy chain and target are the same, check tunnelling and layering + if ((fact.isTunnelled() && !plan.isTunnelled()) || + (fact.isLayered() && !plan.isLayered())) + return UNREACHABLE; + + if (plan.isTunnelled() && !fact.isTunnelled()) + return TUNNEL_TARGET; + if (plan.isLayered() && !fact.isLayered()) + return LAYER_PROTOCOL; + + // tunnel and layering are the same, remains to check the security + // Should we tolerate if security is better than planned? + // (plan.isSecure() && !fact.isSecure()) + if (plan.isSecure() != fact.isSecure()) + return UNREACHABLE; + + return COMPLETE; + } + + +} // class BasicRouteDirector diff --git a/src/org/apache/http/conn/routing/HttpRoute.java b/src/org/apache/http/conn/routing/HttpRoute.java new file mode 100644 index 0000000..1e870b8 --- /dev/null +++ b/src/org/apache/http/conn/routing/HttpRoute.java @@ -0,0 +1,443 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/HttpRoute.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.routing; + +import java.net.InetAddress; + +import org.apache.http.HttpHost; + +/** + * The route for a request. + * Instances of this class are unmodifiable and therefore suitable + * for use as lookup keys. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 653041 $ + * + * @since 4.0 + */ +public final class HttpRoute implements RouteInfo, Cloneable { + + /** The target host to connect to. */ + private final HttpHost targetHost; + + /** + * The local address to connect from. + * <code>null</code> indicates that the default should be used. + */ + private final InetAddress localAddress; + + /** The proxy servers, if any. */ + private final HttpHost[] proxyChain; + + /** Whether the the route is tunnelled through the proxy. */ + private final TunnelType tunnelled; + + /** Whether the route is layered. */ + private final LayerType layered; + + /** Whether the route is (supposed to be) secure. */ + private final boolean secure; + + + /** + * Internal, fully-specified constructor. + * This constructor does <i>not</i> clone the proxy chain array, + * nor test it for <code>null</code> elements. This conversion and + * check is the responsibility of the public constructors. + * The order of arguments here is different from the similar public + * constructor, as required by Java. + * + * @param local the local address to route from, or + * <code>null</code> for the default + * @param target the host to which to route + * @param proxies the proxy chain to use, or + * <code>null</code> for a direct route + * @param secure <code>true</code> if the route is (to be) secure, + * <code>false</code> otherwise + * @param tunnelled the tunnel type of this route, or + * <code>null</code> for PLAIN + * @param layered the layering type of this route, or + * <code>null</code> for PLAIN + */ + private HttpRoute(InetAddress local, + HttpHost target, HttpHost[] proxies, + boolean secure, + TunnelType tunnelled, LayerType layered) { + if (target == null) { + throw new IllegalArgumentException + ("Target host may not be null."); + } + if ((tunnelled == TunnelType.TUNNELLED) && (proxies == null)) { + throw new IllegalArgumentException + ("Proxy required if tunnelled."); + } + + // tunnelled is already checked above, that is in line with the default + if (tunnelled == null) + tunnelled = TunnelType.PLAIN; + if (layered == null) + layered = LayerType.PLAIN; + + this.targetHost = target; + this.localAddress = local; + this.proxyChain = proxies; + this.secure = secure; + this.tunnelled = tunnelled; + this.layered = layered; + } + + + /** + * Creates a new route with all attributes specified explicitly. + * + * @param target the host to which to route + * @param local the local address to route from, or + * <code>null</code> for the default + * @param proxies the proxy chain to use, or + * <code>null</code> for a direct route + * @param secure <code>true</code> if the route is (to be) secure, + * <code>false</code> otherwise + * @param tunnelled the tunnel type of this route + * @param layered the layering type of this route + */ + public HttpRoute(HttpHost target, InetAddress local, HttpHost[] proxies, + boolean secure, TunnelType tunnelled, LayerType layered) { + this(local, target, toChain(proxies), secure, tunnelled, layered); + } + + + /** + * Creates a new route with at most one proxy. + * + * @param target the host to which to route + * @param local the local address to route from, or + * <code>null</code> for the default + * @param proxy the proxy to use, or + * <code>null</code> for a direct route + * @param secure <code>true</code> if the route is (to be) secure, + * <code>false</code> otherwise + * @param tunnelled <code>true</code> if the route is (to be) tunnelled + * via the proxy, + * <code>false</code> otherwise + * @param layered <code>true</code> if the route includes a + * layered protocol, + * <code>false</code> otherwise + */ + public HttpRoute(HttpHost target, InetAddress local, HttpHost proxy, + boolean secure, TunnelType tunnelled, LayerType layered) { + this(local, target, toChain(proxy), secure, tunnelled, layered); + } + + + /** + * Creates a new direct route. + * That is a route without a proxy. + * + * @param target the host to which to route + * @param local the local address to route from, or + * <code>null</code> for the default + * @param secure <code>true</code> if the route is (to be) secure, + * <code>false</code> otherwise + */ + public HttpRoute(HttpHost target, InetAddress local, boolean secure) { + this(local, target, null, secure, TunnelType.PLAIN, LayerType.PLAIN); + } + + + /** + * Creates a new direct insecure route. + * + * @param target the host to which to route + */ + public HttpRoute(HttpHost target) { + this(null, target, null, false, TunnelType.PLAIN, LayerType.PLAIN); + } + + + /** + * Creates a new route through a proxy. + * When using this constructor, the <code>proxy</code> MUST be given. + * For convenience, it is assumed that a secure connection will be + * layered over a tunnel through the proxy. + * + * @param target the host to which to route + * @param local the local address to route from, or + * <code>null</code> for the default + * @param proxy the proxy to use + * @param secure <code>true</code> if the route is (to be) secure, + * <code>false</code> otherwise + */ + public HttpRoute(HttpHost target, InetAddress local, HttpHost proxy, + boolean secure) { + this(local, target, toChain(proxy), secure, + secure ? TunnelType.TUNNELLED : TunnelType.PLAIN, + secure ? LayerType.LAYERED : LayerType.PLAIN); + if (proxy == null) { + throw new IllegalArgumentException + ("Proxy host may not be null."); + } + } + + + /** + * Helper to convert a proxy to a proxy chain. + * + * @param proxy the only proxy in the chain, or <code>null</code> + * + * @return a proxy chain array, or <code>null</code> + */ + private static HttpHost[] toChain(HttpHost proxy) { + if (proxy == null) + return null; + + return new HttpHost[]{ proxy }; + } + + + /** + * Helper to duplicate and check a proxy chain. + * An empty proxy chain is converted to <code>null</code>. + * + * @param proxies the proxy chain to duplicate, or <code>null</code> + * + * @return a new proxy chain array, or <code>null</code> + */ + private static HttpHost[] toChain(HttpHost[] proxies) { + if ((proxies == null) || (proxies.length < 1)) + return null; + + for (HttpHost proxy : proxies) { + if (proxy == null) + throw new IllegalArgumentException + ("Proxy chain may not contain null elements."); + } + + // copy the proxy chain, the traditional way + HttpHost[] result = new HttpHost[proxies.length]; + System.arraycopy(proxies, 0, result, 0, proxies.length); + + return result; + } + + + + // non-JavaDoc, see interface RouteInfo + public final HttpHost getTargetHost() { + return this.targetHost; + } + + + // non-JavaDoc, see interface RouteInfo + public final InetAddress getLocalAddress() { + return this.localAddress; + } + + + // non-JavaDoc, see interface RouteInfo + public final int getHopCount() { + return (proxyChain == null) ? 1 : (proxyChain.length+1); + } + + + // non-JavaDoc, see interface RouteInfo + public final HttpHost getHopTarget(int hop) { + if (hop < 0) + throw new IllegalArgumentException + ("Hop index must not be negative: " + hop); + final int hopcount = getHopCount(); + if (hop >= hopcount) + throw new IllegalArgumentException + ("Hop index " + hop + + " exceeds route length " + hopcount); + + HttpHost result = null; + if (hop < hopcount-1) + result = this.proxyChain[hop]; + else + result = this.targetHost; + + return result; + } + + + // non-JavaDoc, see interface RouteInfo + public final HttpHost getProxyHost() { + return (this.proxyChain == null) ? null : this.proxyChain[0]; + } + + + // non-JavaDoc, see interface RouteInfo + public final TunnelType getTunnelType() { + return this.tunnelled; + } + + + // non-JavaDoc, see interface RouteInfo + public final boolean isTunnelled() { + return (this.tunnelled == TunnelType.TUNNELLED); + } + + + // non-JavaDoc, see interface RouteInfo + public final LayerType getLayerType() { + return this.layered; + } + + + // non-JavaDoc, see interface RouteInfo + public final boolean isLayered() { + return (this.layered == LayerType.LAYERED); + } + + + // non-JavaDoc, see interface RouteInfo + public final boolean isSecure() { + return this.secure; + } + + + /** + * Compares this route to another. + * + * @param o the object to compare with + * + * @return <code>true</code> if the argument is the same route, + * <code>false</code> + */ + @Override + public final boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof HttpRoute)) + return false; + + HttpRoute that = (HttpRoute) o; + boolean equal = this.targetHost.equals(that.targetHost); + equal &= + ( this.localAddress == that.localAddress) || + ((this.localAddress != null) && + this.localAddress.equals(that.localAddress)); + equal &= + ( this.proxyChain == that.proxyChain) || + ((this.proxyChain != null) && + (that.proxyChain != null) && + (this.proxyChain.length == that.proxyChain.length)); + // comparison of actual proxies follows below + equal &= + (this.secure == that.secure) && + (this.tunnelled == that.tunnelled) && + (this.layered == that.layered); + + // chain length has been compared above, now check the proxies + if (equal && (this.proxyChain != null)) { + for (int i=0; equal && (i<this.proxyChain.length); i++) + equal = this.proxyChain[i].equals(that.proxyChain[i]); + } + + return equal; + } + + + /** + * Generates a hash code for this route. + * + * @return the hash code + */ + @Override + public final int hashCode() { + + int hc = this.targetHost.hashCode(); + + if (this.localAddress != null) + hc ^= localAddress.hashCode(); + if (this.proxyChain != null) { + hc ^= proxyChain.length; + for (HttpHost aProxyChain : proxyChain) hc ^= aProxyChain.hashCode(); + } + + if (this.secure) + hc ^= 0x11111111; + + hc ^= this.tunnelled.hashCode(); + hc ^= this.layered.hashCode(); + + return hc; + } + + + /** + * Obtains a description of this route. + * + * @return a human-readable representation of this route + */ + @Override + public final String toString() { + StringBuilder cab = new StringBuilder(50 + getHopCount()*30); + + cab.append("HttpRoute["); + if (this.localAddress != null) { + cab.append(this.localAddress); + cab.append("->"); + } + cab.append('{'); + if (this.tunnelled == TunnelType.TUNNELLED) + cab.append('t'); + if (this.layered == LayerType.LAYERED) + cab.append('l'); + if (this.secure) + cab.append('s'); + cab.append("}->"); + if (this.proxyChain != null) { + for (HttpHost aProxyChain : this.proxyChain) { + cab.append(aProxyChain); + cab.append("->"); + } + } + cab.append(this.targetHost); + cab.append(']'); + + return cab.toString(); + } + + + // default implementation of clone() is sufficient + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + +} // class HttpRoute diff --git a/src/org/apache/http/conn/routing/HttpRouteDirector.java b/src/org/apache/http/conn/routing/HttpRouteDirector.java new file mode 100644 index 0000000..8cfcf67 --- /dev/null +++ b/src/org/apache/http/conn/routing/HttpRouteDirector.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/HttpRouteDirector.java $ + * $Revision: 620255 $ + * $Date: 2008-02-10 02:23:55 -0800 (Sun, 10 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.routing; + + + +/** + * Provides directions on establishing a route. + * Implementations of this interface compare a planned route with + * a tracked route and indicate the next step required. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 620255 $ + * + * @since 4.0 + */ +public interface HttpRouteDirector { + + /** Indicates that the route can not be established at all. */ + public final static int UNREACHABLE = -1; + + /** Indicates that the route is complete. */ + public final static int COMPLETE = 0; + + /** Step: open connection to target. */ + public final static int CONNECT_TARGET = 1; + + /** Step: open connection to proxy. */ + public final static int CONNECT_PROXY = 2; + + /** Step: tunnel through proxy to target. */ + public final static int TUNNEL_TARGET = 3; + + /** Step: tunnel through proxy to other proxy. */ + public final static int TUNNEL_PROXY = 4; + + /** Step: layer protocol (over tunnel). */ + public final static int LAYER_PROTOCOL = 5; + + + /** + * Provides the next step. + * + * @param plan the planned route + * @param fact the currently established route, or + * <code>null</code> if nothing is established + * + * @return one of the constants defined in this interface, indicating + * either the next step to perform, or success, or failure. + * 0 is for success, a negative value for failure. + */ + public int nextStep(RouteInfo plan, RouteInfo fact) + ; + + +} // interface HttpRouteDirector diff --git a/src/org/apache/http/conn/routing/HttpRoutePlanner.java b/src/org/apache/http/conn/routing/HttpRoutePlanner.java new file mode 100644 index 0000000..489702a --- /dev/null +++ b/src/org/apache/http/conn/routing/HttpRoutePlanner.java @@ -0,0 +1,69 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/HttpRoutePlanner.java $ + * $Revision: 613654 $ + * $Date: 2008-01-20 11:00:19 -0800 (Sun, 20 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.routing; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; + + + +/** + * Encapsulates logic to compute a {@link HttpRoute} to a target host. + * Implementations may for example be based on parameters, or on the + * standard Java system properties. + */ +public interface HttpRoutePlanner { + + /** + * Determines the route for a request. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context to use for the subsequent execution. + * Implementations may accept <code>null</code>. + * + * @return the route that the request should take + * + * @throws HttpException in case of a problem + */ + public HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException + ; + +} diff --git a/src/org/apache/http/conn/routing/RouteInfo.java b/src/org/apache/http/conn/routing/RouteInfo.java new file mode 100644 index 0000000..3449cb1 --- /dev/null +++ b/src/org/apache/http/conn/routing/RouteInfo.java @@ -0,0 +1,194 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/RouteInfo.java $ + * $Revision: 652200 $ + * $Date: 2008-04-29 17:22:43 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.routing; + +import java.net.InetAddress; + +import org.apache.http.HttpHost; + + +/** + * Read-only interface for route information. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 652200 $ + * + * @since 4.0 + */ +public interface RouteInfo { + + /** + * The tunnelling type of a route. + * Plain routes are established by connecting to the target or + * the first proxy. + * Tunnelled routes are established by connecting to the first proxy + * and tunnelling through all proxies to the target. + * Routes without a proxy cannot be tunnelled. + */ + public enum TunnelType { PLAIN, TUNNELLED } + + /** + * The layering type of a route. + * Plain routes are established by connecting or tunnelling. + * Layered routes are established by layering a protocol such as TLS/SSL + * over an existing connection. + * Protocols can only be layered over a tunnel to the target, or + * or over a direct connection without proxies. + * <br/> + * Layering a protocol + * over a direct connection makes little sense, since the connection + * could be established with the new protocol in the first place. + * But we don't want to exclude that use case. + */ + public enum LayerType { PLAIN, LAYERED } + + + + /** + * Obtains the target host. + * + * @return the target host + */ + HttpHost getTargetHost() + ; + + + /** + * Obtains the local address to connect from. + * + * @return the local address, + * or <code>null</code> + */ + InetAddress getLocalAddress() + ; + + + /** + * Obtains the number of hops in this route. + * A direct route has one hop. A route through a proxy has two hops. + * A route through a chain of <i>n</i> proxies has <i>n+1</i> hops. + * + * @return the number of hops in this route + */ + int getHopCount() + ; + + + /** + * Obtains the target of a hop in this route. + * The target of the last hop is the {@link #getTargetHost target host}, + * the target of previous hops is the respective proxy in the chain. + * For a route through exactly one proxy, target of hop 0 is the proxy + * and target of hop 1 is the target host. + * + * @param hop index of the hop for which to get the target, + * 0 for first + * + * @return the target of the given hop + * + * @throws IllegalArgumentException + * if the argument is negative or not less than + * {@link #getHopCount getHopCount()} + */ + HttpHost getHopTarget(int hop) + ; + + + /** + * Obtains the first proxy host. + * + * @return the first proxy in the proxy chain, or + * <code>null</code> if this route is direct + */ + HttpHost getProxyHost() + ; + + + /** + * Obtains the tunnel type of this route. + * If there is a proxy chain, only end-to-end tunnels are considered. + * + * @return the tunnelling type + */ + TunnelType getTunnelType() + ; + + + /** + * Checks whether this route is tunnelled through a proxy. + * If there is a proxy chain, only end-to-end tunnels are considered. + * + * @return <code>true</code> if tunnelled end-to-end through at least + * one proxy, + * <code>false</code> otherwise + */ + boolean isTunnelled() + ; + + + /** + * Obtains the layering type of this route. + * In the presence of proxies, only layering over an end-to-end tunnel + * is considered. + * + * @return the layering type + */ + LayerType getLayerType() + ; + + + /** + * Checks whether this route includes a layered protocol. + * In the presence of proxies, only layering over an end-to-end tunnel + * is considered. + * + * @return <code>true</code> if layered, + * <code>false</code> otherwise + */ + boolean isLayered() + ; + + + /** + * Checks whether this route is secure. + * + * @return <code>true</code> if secure, + * <code>false</code> otherwise + */ + boolean isSecure() + ; + + +} // interface RouteInfo diff --git a/src/org/apache/http/conn/routing/RouteTracker.java b/src/org/apache/http/conn/routing/RouteTracker.java new file mode 100644 index 0000000..ba8213e --- /dev/null +++ b/src/org/apache/http/conn/routing/RouteTracker.java @@ -0,0 +1,439 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/RouteTracker.java $ + * $Revision: 620254 $ + * $Date: 2008-02-10 02:18:48 -0800 (Sun, 10 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.routing; + +import java.net.InetAddress; + +import org.apache.http.HttpHost; + + +/** + * Helps tracking the steps in establishing a route. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 620254 $ + * + * @since 4.0 + */ +public final class RouteTracker implements RouteInfo, Cloneable { + + /** The target host to connect to. */ + private final HttpHost targetHost; + + /** + * The local address to connect from. + * <code>null</code> indicates that the default should be used. + */ + private final InetAddress localAddress; + + // the attributes above are fixed at construction time + // now follow attributes that indicate the established route + + /** Whether the first hop of the route is established. */ + private boolean connected; + + /** The proxy chain, if any. */ + private HttpHost[] proxyChain; + + /** Whether the the route is tunnelled end-to-end through proxies. */ + private TunnelType tunnelled; + + /** Whether the route is layered over a tunnel. */ + private LayerType layered; + + /** Whether the route is secure. */ + private boolean secure; + + + /** + * Creates a new route tracker. + * The target and origin need to be specified at creation time. + * + * @param target the host to which to route + * @param local the local address to route from, or + * <code>null</code> for the default + */ + public RouteTracker(HttpHost target, InetAddress local) { + if (target == null) { + throw new IllegalArgumentException("Target host may not be null."); + } + this.targetHost = target; + this.localAddress = local; + this.tunnelled = TunnelType.PLAIN; + this.layered = LayerType.PLAIN; + } + + + /** + * Creates a new tracker for the given route. + * Only target and origin are taken from the route, + * everything else remains to be tracked. + * + * @param route the route to track + */ + public RouteTracker(HttpRoute route) { + this(route.getTargetHost(), route.getLocalAddress()); + } + + + /** + * Tracks connecting to the target. + * + * @param secure <code>true</code> if the route is secure, + * <code>false</code> otherwise + */ + public final void connectTarget(boolean secure) { + if (this.connected) { + throw new IllegalStateException("Already connected."); + } + this.connected = true; + this.secure = secure; + } + + + /** + * Tracks connecting to the first proxy. + * + * @param proxy the proxy connected to + * @param secure <code>true</code> if the route is secure, + * <code>false</code> otherwise + */ + public final void connectProxy(HttpHost proxy, boolean secure) { + if (proxy == null) { + throw new IllegalArgumentException("Proxy host may not be null."); + } + if (this.connected) { + throw new IllegalStateException("Already connected."); + } + this.connected = true; + this.proxyChain = new HttpHost[]{ proxy }; + this.secure = secure; + } + + + /** + * Tracks tunnelling to the target. + * + * @param secure <code>true</code> if the route is secure, + * <code>false</code> otherwise + */ + public final void tunnelTarget(boolean secure) { + if (!this.connected) { + throw new IllegalStateException("No tunnel unless connected."); + } + if (this.proxyChain == null) { + throw new IllegalStateException("No tunnel without proxy."); + } + this.tunnelled = TunnelType.TUNNELLED; + this.secure = secure; + } + + + /** + * Tracks tunnelling to a proxy in a proxy chain. + * This will extend the tracked proxy chain, but it does not mark + * the route as tunnelled. Only end-to-end tunnels are considered there. + * + * @param proxy the proxy tunnelled to + * @param secure <code>true</code> if the route is secure, + * <code>false</code> otherwise + */ + public final void tunnelProxy(HttpHost proxy, boolean secure) { + if (proxy == null) { + throw new IllegalArgumentException("Proxy host may not be null."); + } + if (!this.connected) { + throw new IllegalStateException("No tunnel unless connected."); + } + if (this.proxyChain == null) { + throw new IllegalStateException("No proxy tunnel without proxy."); + } + + // prepare an extended proxy chain + HttpHost[] proxies = new HttpHost[this.proxyChain.length+1]; + System.arraycopy(this.proxyChain, 0, + proxies, 0, this.proxyChain.length); + proxies[proxies.length-1] = proxy; + + this.proxyChain = proxies; + this.secure = secure; + } + + + /** + * Tracks layering a protocol. + * + * @param secure <code>true</code> if the route is secure, + * <code>false</code> otherwise + */ + public final void layerProtocol(boolean secure) { + // it is possible to layer a protocol over a direct connection, + // although this case is probably not considered elsewhere + if (!this.connected) { + throw new IllegalStateException + ("No layered protocol unless connected."); + } + this.layered = LayerType.LAYERED; + this.secure = secure; + } + + + + // non-JavaDoc, see interface RouteInfo + public final HttpHost getTargetHost() { + return this.targetHost; + } + + + // non-JavaDoc, see interface RouteInfo + public final InetAddress getLocalAddress() { + return this.localAddress; + } + + + // non-JavaDoc, see interface RouteInfo + public final int getHopCount() { + int hops = 0; + if (this.connected) { + if (proxyChain == null) + hops = 1; + else + hops = proxyChain.length + 1; + } + return hops; + } + + + // non-JavaDoc, see interface RouteInfo + public final HttpHost getHopTarget(int hop) { + if (hop < 0) + throw new IllegalArgumentException + ("Hop index must not be negative: " + hop); + final int hopcount = getHopCount(); + if (hop >= hopcount) { + throw new IllegalArgumentException + ("Hop index " + hop + + " exceeds tracked route length " + hopcount +"."); + } + + HttpHost result = null; + if (hop < hopcount-1) + result = this.proxyChain[hop]; + else + result = this.targetHost; + + return result; + } + + + // non-JavaDoc, see interface RouteInfo + public final HttpHost getProxyHost() { + return (this.proxyChain == null) ? null : this.proxyChain[0]; + } + + + // non-JavaDoc, see interface RouteInfo + public final boolean isConnected() { + return this.connected; + } + + + // non-JavaDoc, see interface RouteInfo + public final TunnelType getTunnelType() { + return this.tunnelled; + } + + + // non-JavaDoc, see interface RouteInfo + public final boolean isTunnelled() { + return (this.tunnelled == TunnelType.TUNNELLED); + } + + + // non-JavaDoc, see interface RouteInfo + public final LayerType getLayerType() { + return this.layered; + } + + + // non-JavaDoc, see interface RouteInfo + public final boolean isLayered() { + return (this.layered == LayerType.LAYERED); + } + + + // non-JavaDoc, see interface RouteInfo + public final boolean isSecure() { + return this.secure; + } + + + /** + * Obtains the tracked route. + * If a route has been tracked, it is {@link #isConnected connected}. + * If not connected, nothing has been tracked so far. + * + * @return the tracked route, or + * <code>null</code> if nothing has been tracked so far + */ + public final HttpRoute toRoute() { + return !this.connected ? + null : new HttpRoute(this.targetHost, this.localAddress, + this.proxyChain, this.secure, + this.tunnelled, this.layered); + } + + + /** + * Compares this tracked route to another. + * + * @param o the object to compare with + * + * @return <code>true</code> if the argument is the same tracked route, + * <code>false</code> + */ + @Override + public final boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof RouteTracker)) + return false; + + RouteTracker that = (RouteTracker) o; + boolean equal = this.targetHost.equals(that.targetHost); + equal &= + ( this.localAddress == that.localAddress) || + ((this.localAddress != null) && + this.localAddress.equals(that.localAddress)); + equal &= + ( this.proxyChain == that.proxyChain) || + ((this.proxyChain != null) && + (that.proxyChain != null) && + (this.proxyChain.length == that.proxyChain.length)); + // comparison of actual proxies follows below + equal &= + (this.connected == that.connected) && + (this.secure == that.secure) && + (this.tunnelled == that.tunnelled) && + (this.layered == that.layered); + + // chain length has been compared above, now check the proxies + if (equal && (this.proxyChain != null)) { + for (int i=0; equal && (i<this.proxyChain.length); i++) + equal = this.proxyChain[i].equals(that.proxyChain[i]); + } + + return equal; + } + + + /** + * Generates a hash code for this tracked route. + * Route trackers are modifiable and should therefore not be used + * as lookup keys. Use {@link #toRoute toRoute} to obtain an + * unmodifiable representation of the tracked route. + * + * @return the hash code + */ + @Override + public final int hashCode() { + + int hc = this.targetHost.hashCode(); + + if (this.localAddress != null) + hc ^= localAddress.hashCode(); + if (this.proxyChain != null) { + hc ^= proxyChain.length; + for (int i=0; i<proxyChain.length; i++) + hc ^= proxyChain[i].hashCode(); + } + + if (this.connected) + hc ^= 0x11111111; + if (this.secure) + hc ^= 0x22222222; + + hc ^= this.tunnelled.hashCode(); + hc ^= this.layered.hashCode(); + + return hc; + } + + + /** + * Obtains a description of the tracked route. + * + * @return a human-readable representation of the tracked route + */ + @Override + public final String toString() { + StringBuilder cab = new StringBuilder(50 + getHopCount()*30); + + cab.append("RouteTracker["); + if (this.localAddress != null) { + cab.append(this.localAddress); + cab.append("->"); + } + cab.append('{'); + if (this.connected) + cab.append('c'); + if (this.tunnelled == TunnelType.TUNNELLED) + cab.append('t'); + if (this.layered == LayerType.LAYERED) + cab.append('l'); + if (this.secure) + cab.append('s'); + cab.append("}->"); + if (this.proxyChain != null) { + for (int i=0; i<this.proxyChain.length; i++) { + cab.append(this.proxyChain[i]); + cab.append("->"); + } + } + cab.append(this.targetHost); + cab.append(']'); + + return cab.toString(); + } + + + // default implementation of clone() is sufficient + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + +} // class RouteTracker diff --git a/src/org/apache/http/conn/routing/package.html b/src/org/apache/http/conn/routing/package.html new file mode 100644 index 0000000..b50f97c --- /dev/null +++ b/src/org/apache/http/conn/routing/package.html @@ -0,0 +1,62 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/package.html $ + * $Revision: 613656 $ + * $Date: 2008-01-20 11:06:56 -0800 (Sun, 20 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The client-side route representation and tracking API, part of <i>HttpConn</i>. + +<p> +An {@link org.apache.http.conn.routing.HttpRoute HttpRoute} +is the path along which a request has to be sent to the server. +The route starts at a local network address and may pass +through one or more proxies before reaching the target. +Routes through proxies can be tunnelled, and a layered protocol (TLS/SSL) +might be put on top of the tunnel. +The {@link org.apache.http.conn.routing.RouteTracker RouteTracker} +helps in tracking the steps for establishing a route, while an +{@link org.apache.http.conn.routing.HttpRouteDirector HttpRouteDirector} +determines the next step to take. +</p> + + +<p> +The {@link org.apache.http.conn.routing.HttpRoutePlanner HttpRoutePlanner} +is responsible for determining a route to a given target host. +Implementations must know about proxies to use, and about exemptions +for hosts that should be contacted directly without a proxy. +</p> + + +</body> +</html> diff --git a/src/org/apache/http/conn/scheme/HostNameResolver.java b/src/org/apache/http/conn/scheme/HostNameResolver.java new file mode 100644 index 0000000..ca6615c --- /dev/null +++ b/src/org/apache/http/conn/scheme/HostNameResolver.java @@ -0,0 +1,41 @@ +/* + * $HeadURL:$ + * $Revision:$ + * $Date:$ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.scheme; + +import java.io.IOException; +import java.net.InetAddress; + +public interface HostNameResolver { + + InetAddress resolve (String hostname) throws IOException; + +} diff --git a/src/org/apache/http/conn/scheme/LayeredSocketFactory.java b/src/org/apache/http/conn/scheme/LayeredSocketFactory.java new file mode 100644 index 0000000..8dc6c6c --- /dev/null +++ b/src/org/apache/http/conn/scheme/LayeredSocketFactory.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactory.java $ + * $Revision: 645850 $ + * $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.scheme; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * A {@link SocketFactory SocketFactory} for layered sockets (SSL/TLS). + * See there for things to consider when implementing a socket factory. + * + * @author Michael Becke + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @since 4.0 + */ +public interface LayeredSocketFactory extends SocketFactory { + + /** + * Returns a socket connected to the given host that is layered over an + * existing socket. Used primarily for creating secure sockets through + * proxies. + * + * @param socket the existing socket + * @param host the host name/IP + * @param port the port on the host + * @param autoClose a flag for closing the underling socket when the created + * socket is closed + * + * @return Socket a new socket + * + * @throws IOException if an I/O error occurs while creating the socket + * @throws UnknownHostException if the IP address of the host cannot be + * determined + */ + Socket createSocket( + Socket socket, + String host, + int port, + boolean autoClose + ) throws IOException, UnknownHostException; + +} diff --git a/src/org/apache/http/conn/scheme/PlainSocketFactory.java b/src/org/apache/http/conn/scheme/PlainSocketFactory.java new file mode 100644 index 0000000..acc13f7 --- /dev/null +++ b/src/org/apache/http/conn/scheme/PlainSocketFactory.java @@ -0,0 +1,182 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/PlainSocketFactory.java $ + * $Revision: 659194 $ + * $Date: 2008-05-22 11:33:47 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.scheme; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; + +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * The default class for creating sockets. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author Michael Becke + */ +public final class PlainSocketFactory implements SocketFactory { + + /** + * The factory singleton. + */ + private static final + PlainSocketFactory DEFAULT_FACTORY = new PlainSocketFactory(); + + private final HostNameResolver nameResolver; + + /** + * Gets the singleton instance of this class. + * @return the one and only plain socket factory + */ + public static PlainSocketFactory getSocketFactory() { + return DEFAULT_FACTORY; + } + + public PlainSocketFactory(final HostNameResolver nameResolver) { + super(); + this.nameResolver = nameResolver; + } + + + public PlainSocketFactory() { + this(null); + } + + // non-javadoc, see interface org.apache.http.conn.SocketFactory + public Socket createSocket() { + return new Socket(); + } + + // non-javadoc, see interface org.apache.http.conn.SocketFactory + public Socket connectSocket(Socket sock, String host, int port, + InetAddress localAddress, int localPort, + HttpParams params) + throws IOException { + + if (host == null) { + throw new IllegalArgumentException("Target host may not be null."); + } + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null."); + } + + if (sock == null) + sock = createSocket(); + + if ((localAddress != null) || (localPort > 0)) { + + // we need to bind explicitly + if (localPort < 0) + localPort = 0; // indicates "any" + + InetSocketAddress isa = + new InetSocketAddress(localAddress, localPort); + sock.bind(isa); + } + + int timeout = HttpConnectionParams.getConnectionTimeout(params); + + InetSocketAddress remoteAddress; + if (this.nameResolver != null) { + remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port); + } else { + remoteAddress = new InetSocketAddress(host, port); + } + + sock.connect(remoteAddress, timeout); + + return sock; + + } // connectSocket + + + /** + * Checks whether a socket connection is secure. + * This factory creates plain socket connections + * which are not considered secure. + * + * @param sock the connected socket + * + * @return <code>false</code> + * + * @throws IllegalArgumentException if the argument is invalid + */ + public final boolean isSecure(Socket sock) + throws IllegalArgumentException { + + if (sock == null) { + throw new IllegalArgumentException("Socket may not be null."); + } + // This class check assumes that createSocket() calls the constructor + // directly. If it was using javax.net.SocketFactory, we couldn't make + // an assumption about the socket class here. + if (sock.getClass() != Socket.class) { + throw new IllegalArgumentException + ("Socket not created by this factory."); + } + // This check is performed last since it calls a method implemented + // by the argument object. getClass() is final in java.lang.Object. + if (sock.isClosed()) { + throw new IllegalArgumentException("Socket is closed."); + } + + return false; + + } // isSecure + + + /** + * Compares this factory with an object. + * There is only one instance of this class. + * + * @param obj the object to compare with + * + * @return iff the argument is this object + */ + @Override + public boolean equals(Object obj) { + return (obj == this); + } + + /** + * Obtains a hash code for this object. + * All instances of this class have the same hash code. + * There is only one instance of this class. + */ + @Override + public int hashCode() { + return PlainSocketFactory.class.hashCode(); + } + +} diff --git a/src/org/apache/http/conn/scheme/Scheme.java b/src/org/apache/http/conn/scheme/Scheme.java new file mode 100644 index 0000000..590d59d --- /dev/null +++ b/src/org/apache/http/conn/scheme/Scheme.java @@ -0,0 +1,223 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/Scheme.java $ + * $Revision: 652950 $ + * $Date: 2008-05-02 16:49:48 -0700 (Fri, 02 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.conn.scheme; + +import java.util.Locale; + +import org.apache.http.util.LangUtils; + +/** + * Encapsulates specifics of a protocol scheme such as "http" or "https". + * Schemes are identified by lowercase names. + * Supported schemes are typically collected in a + * {@link SchemeRegistry SchemeRegistry}. + * + * <p> + * For example, to configure support for "https://" URLs, + * you could write code like the following: + * </p> + * <pre> + * Scheme https = new Scheme("https", new MySecureSocketFactory(), 443); + * SchemeRegistry.DEFAULT.register(https); + * </pre> + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author Michael Becke + * @author Jeff Dever + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + */ +public final class Scheme { + + /** The name of this scheme, in lowercase. (e.g. http, https) */ + private final String name; + + /** The socket factory for this scheme */ + private final SocketFactory socketFactory; + + /** The default port for this scheme */ + private final int defaultPort; + + /** Indicates whether this scheme allows for layered connections */ + private final boolean layered; + + + /** A string representation, for {@link #toString toString}. */ + private String stringRep; + + + /** + * Creates a new scheme. + * Whether the created scheme allows for layered connections + * depends on the class of <code>factory</code>. + * + * @param name the scheme name, for example "http". + * The name will be converted to lowercase. + * @param factory the factory for creating sockets for communication + * with this scheme + * @param port the default port for this scheme + */ + public Scheme(final String name, + final SocketFactory factory, + final int port) { + + if (name == null) { + throw new IllegalArgumentException + ("Scheme name may not be null"); + } + if (factory == null) { + throw new IllegalArgumentException + ("Socket factory may not be null"); + } + if ((port <= 0) || (port > 0xffff)) { + throw new IllegalArgumentException + ("Port is invalid: " + port); + } + + this.name = name.toLowerCase(Locale.ENGLISH); + this.socketFactory = factory; + this.defaultPort = port; + this.layered = (factory instanceof LayeredSocketFactory); + } + + + /** + * Obtains the default port. + * + * @return the default port for this scheme + */ + public final int getDefaultPort() { + return defaultPort; + } + + + /** + * Obtains the socket factory. + * If this scheme is {@link #isLayered layered}, the factory implements + * {@link LayeredSocketFactory LayeredSocketFactory}. + * + * @return the socket factory for this scheme + */ + public final SocketFactory getSocketFactory() { + return socketFactory; + } + + + /** + * Obtains the scheme name. + * + * @return the name of this scheme, in lowercase + */ + public final String getName() { + return name; + } + + + /** + * Indicates whether this scheme allows for layered connections. + * + * @return <code>true</code> if layered connections are possible, + * <code>false</code> otherwise + */ + public final boolean isLayered() { + return layered; + } + + + /** + * Resolves the correct port for this scheme. + * Returns the given port if it is valid, the default port otherwise. + * + * @param port the port to be resolved, + * a negative number to obtain the default port + * + * @return the given port or the defaultPort + */ + public final int resolvePort(int port) { + return ((port <= 0) || (port > 0xffff)) ? defaultPort : port; + } + + + /** + * Return a string representation of this object. + * + * @return a human-readable string description of this scheme + */ + @Override + public final String toString() { + if (stringRep == null) { + StringBuilder buffer = new StringBuilder(); + buffer.append(this.name); + buffer.append(':'); + buffer.append(Integer.toString(this.defaultPort)); + stringRep = buffer.toString(); + } + return stringRep; + } + + + /** + * Compares this scheme to an object. + * + * @param obj the object to compare with + * + * @return <code>true</code> iff the argument is equal to this scheme + */ + @Override + public final boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof Scheme)) return false; + + Scheme s = (Scheme) obj; + return (name.equals(s.name) && + defaultPort == s.defaultPort && + layered == s.layered && + socketFactory.equals(s.socketFactory) + ); + } // equals + + + /** + * Obtains a hash code for this scheme. + * + * @return the hash code + */ + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.defaultPort); + hash = LangUtils.hashCode(hash, this.name); + hash = LangUtils.hashCode(hash, this.layered); + hash = LangUtils.hashCode(hash, this.socketFactory); + return hash; + } + +} // class Scheme diff --git a/src/org/apache/http/conn/scheme/SchemeRegistry.java b/src/org/apache/http/conn/scheme/SchemeRegistry.java new file mode 100644 index 0000000..2ee8685 --- /dev/null +++ b/src/org/apache/http/conn/scheme/SchemeRegistry.java @@ -0,0 +1,187 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/SchemeRegistry.java $ + * $Revision: 648356 $ + * $Date: 2008-04-15 10:57:53 -0700 (Tue, 15 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.conn.scheme; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.http.HttpHost; + +/** + * A set of supported protocol {@link Scheme schemes}. + * Schemes are identified by lowercase names. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 648356 $ $Date: 2008-04-15 10:57:53 -0700 (Tue, 15 Apr 2008) $ + * + * @since 4.0 + */ +public final class SchemeRegistry { + + /** The available schemes in this registry. */ + private final Map<String,Scheme> registeredSchemes; + + + /** + * Creates a new, empty scheme registry. + */ + public SchemeRegistry() { + super(); + registeredSchemes = new LinkedHashMap<String,Scheme>(); + } + + + /** + * Obtains a scheme by name. + * + * @param name the name of the scheme to look up (in lowercase) + * + * @return the scheme, never <code>null</code> + * + * @throws IllegalStateException + * if the scheme with the given name is not registered + */ + public synchronized final Scheme getScheme(String name) { + Scheme found = get(name); + if (found == null) { + throw new IllegalStateException + ("Scheme '"+name+"' not registered."); + } + return found; + } + + + /** + * Obtains the scheme for a host. + * Convenience method for <code>getScheme(host.getSchemeName())</pre> + * + * @param host the host for which to obtain the scheme + * + * @return the scheme for the given host, never <code>null</code> + * + * @throws IllegalStateException + * if a scheme with the respective name is not registered + */ + public synchronized final Scheme getScheme(HttpHost host) { + if (host == null) { + throw new IllegalArgumentException("Host must not be null."); + } + return getScheme(host.getSchemeName()); + } + + + /** + * Obtains a scheme by name, if registered. + * + * @param name the name of the scheme to look up (in lowercase) + * + * @return the scheme, or + * <code>null</code> if there is none by this name + */ + public synchronized final Scheme get(String name) { + if (name == null) + throw new IllegalArgumentException("Name must not be null."); + + // leave it to the caller to use the correct name - all lowercase + //name = name.toLowerCase(); + Scheme found = registeredSchemes.get(name); + return found; + } + + + /** + * Registers a scheme. + * The scheme can later be retrieved by its name + * using {@link #getScheme(String) getScheme} or {@link #get get}. + * + * @param sch the scheme to register + * + * @return the scheme previously registered with that name, or + * <code>null</code> if none was registered + */ + public synchronized final Scheme register(Scheme sch) { + if (sch == null) + throw new IllegalArgumentException("Scheme must not be null."); + + Scheme old = registeredSchemes.put(sch.getName(), sch); + return old; + } + + + /** + * Unregisters a scheme. + * + * @param name the name of the scheme to unregister (in lowercase) + * + * @return the unregistered scheme, or + * <code>null</code> if there was none + */ + public synchronized final Scheme unregister(String name) { + if (name == null) + throw new IllegalArgumentException("Name must not be null."); + + // leave it to the caller to use the correct name - all lowercase + //name = name.toLowerCase(); + Scheme gone = registeredSchemes.remove(name); + return gone; + } + + + /** + * Obtains the names of the registered schemes in their default order. + * + * @return List containing registered scheme names. + */ + public synchronized final List<String> getSchemeNames() { + return new ArrayList<String>(registeredSchemes.keySet()); + } + + /** + * Populates the internal collection of registered {@link Scheme protocol schemes} + * with the content of the map passed as a parameter. + * + * @param map protocol schemes + */ + public synchronized void setItems(final Map<String, Scheme> map) { + if (map == null) { + return; + } + registeredSchemes.clear(); + registeredSchemes.putAll(map); + } + +} // class SchemeRegistry + diff --git a/src/org/apache/http/conn/scheme/SocketFactory.java b/src/org/apache/http/conn/scheme/SocketFactory.java new file mode 100644 index 0000000..bb553b2 --- /dev/null +++ b/src/org/apache/http/conn/scheme/SocketFactory.java @@ -0,0 +1,138 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/SocketFactory.java $ + * $Revision: 645850 $ + * $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.scheme; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.params.HttpParams; + +/** + * A factory for creating and connecting sockets. + * The factory encapsulates the logic for establishing a socket connection. + * <br/> + * Both {@link java.lang.Object#equals(java.lang.Object) Object.equals()} + * and {@link java.lang.Object#hashCode() Object.hashCode()} + * must be overridden for the correct operation of some connection managers. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author Michael Becke + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + */ +public interface SocketFactory { + + /** + * Creates a new, unconnected socket. + * The socket should subsequently be passed to + * {@link #connectSocket connectSocket}. + * + * @return a new socket + * + * @throws IOException if an I/O error occurs while creating the socket + */ + Socket createSocket() + throws IOException + ; + + + /** + * Connects a socket to the given host. + * + * @param sock the socket to connect, as obtained from + * {@link #createSocket createSocket}. + * <code>null</code> indicates that a new socket + * should be created and connected. + * @param host the host to connect to + * @param port the port to connect to on the host + * @param localAddress the local address to bind the socket to, or + * <code>null</code> for any + * @param localPort the port on the local machine, + * 0 or a negative number for any + * @param params additional {@link HttpParams parameters} for connecting + * + * @return the connected socket. The returned object may be different + * from the <code>sock</code> argument if this factory supports + * a layered protocol. + * + * @throws IOException if an I/O error occurs + * @throws UnknownHostException if the IP address of the target host + * can not be determined + * @throws ConnectTimeoutException if the socket cannot be connected + * within the time limit defined in the <code>params</code> + */ + Socket connectSocket( + Socket sock, + String host, + int port, + InetAddress localAddress, + int localPort, + HttpParams params + ) throws IOException, UnknownHostException, ConnectTimeoutException; + + + /** + * Checks whether a socket provides a secure connection. + * The socket must be {@link #connectSocket connected} + * by this factory. + * The factory will <i>not</i> perform I/O operations + * in this method. + * <br/> + * As a rule of thumb, plain sockets are not secure and + * TLS/SSL sockets are secure. However, there may be + * application specific deviations. For example, a plain + * socket to a host in the same intranet ("trusted zone") + * could be considered secure. On the other hand, a + * TLS/SSL socket could be considered insecure based on + * the cypher suite chosen for the connection. + * + * @param sock the connected socket to check + * + * @return <code>true</code> if the connection of the socket + * should be considered secure, or + * <code>false</code> if it should not + * + * @throws IllegalArgumentException + * if the argument is invalid, for example because it is + * not a connected socket or was created by a different + * socket factory. + * Note that socket factories are <i>not</i> required to + * check these conditions, they may simply return a default + * value when called with an invalid socket argument. + */ + boolean isSecure(Socket sock) + throws IllegalArgumentException + ; + +} diff --git a/src/org/apache/http/conn/ssl/AbstractVerifier.java b/src/org/apache/http/conn/ssl/AbstractVerifier.java new file mode 100644 index 0000000..5195e58 --- /dev/null +++ b/src/org/apache/http/conn/ssl/AbstractVerifier.java @@ -0,0 +1,343 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.ssl; + +import org.apache.http.conn.util.InetAddressUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.StringTokenizer; +import java.util.logging.Logger; +import java.util.logging.Level; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +/** + * Abstract base class for all standard {@link X509HostnameVerifier} + * implementations. + * + * @author Julius Davies + */ +public abstract class AbstractVerifier implements X509HostnameVerifier { + + /** + * This contains a list of 2nd-level domains that aren't allowed to + * have wildcards when combined with country-codes. + * For example: [*.co.uk]. + * <p/> + * The [*.co.uk] problem is an interesting one. Should we just hope + * that CA's would never foolishly allow such a certificate to happen? + * Looks like we're the only implementation guarding against this. + * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check. + */ + private final static String[] BAD_COUNTRY_2LDS = + { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info", + "lg", "ne", "net", "or", "org" }; + + static { + // Just in case developer forgot to manually sort the array. :-) + Arrays.sort(BAD_COUNTRY_2LDS); + } + + public AbstractVerifier() { + super(); + } + + public final void verify(String host, SSLSocket ssl) + throws IOException { + if(host == null) { + throw new NullPointerException("host to verify is null"); + } + + ssl.startHandshake(); + SSLSession session = ssl.getSession(); + if(session == null) { + // In our experience this only happens under IBM 1.4.x when + // spurious (unrelated) certificates show up in the server' + // chain. Hopefully this will unearth the real problem: + InputStream in = ssl.getInputStream(); + in.available(); + /* + If you're looking at the 2 lines of code above because + you're running into a problem, you probably have two + options: + + #1. Clean up the certificate chain that your server + is presenting (e.g. edit "/etc/apache2/server.crt" + or wherever it is your server's certificate chain + is defined). + + OR + + #2. Upgrade to an IBM 1.5.x or greater JVM, or switch + to a non-IBM JVM. + */ + + // If ssl.getInputStream().available() didn't cause an + // exception, maybe at least now the session is available? + session = ssl.getSession(); + if(session == null) { + // If it's still null, probably a startHandshake() will + // unearth the real problem. + ssl.startHandshake(); + + // Okay, if we still haven't managed to cause an exception, + // might as well go for the NPE. Or maybe we're okay now? + session = ssl.getSession(); + } + } + + Certificate[] certs = session.getPeerCertificates(); + X509Certificate x509 = (X509Certificate) certs[0]; + verify(host, x509); + } + + public final boolean verify(String host, SSLSession session) { + try { + Certificate[] certs = session.getPeerCertificates(); + X509Certificate x509 = (X509Certificate) certs[0]; + verify(host, x509); + return true; + } + catch(SSLException e) { + return false; + } + } + + public final void verify(String host, X509Certificate cert) + throws SSLException { + String[] cns = getCNs(cert); + String[] subjectAlts = getDNSSubjectAlts(cert); + verify(host, cns, subjectAlts); + } + + public final void verify(final String host, final String[] cns, + final String[] subjectAlts, + final boolean strictWithSubDomains) + throws SSLException { + + // Build the list of names we're going to check. Our DEFAULT and + // STRICT implementations of the HostnameVerifier only use the + // first CN provided. All other CNs are ignored. + // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way). + LinkedList<String> names = new LinkedList<String>(); + if(cns != null && cns.length > 0 && cns[0] != null) { + names.add(cns[0]); + } + if(subjectAlts != null) { + for (String subjectAlt : subjectAlts) { + if (subjectAlt != null) { + names.add(subjectAlt); + } + } + } + + if(names.isEmpty()) { + String msg = "Certificate for <" + host + "> doesn't contain CN or DNS subjectAlt"; + throw new SSLException(msg); + } + + // StringBuffer for building the error message. + StringBuffer buf = new StringBuffer(); + + // We're can be case-insensitive when comparing the host we used to + // establish the socket to the hostname in the certificate. + String hostName = host.trim().toLowerCase(Locale.ENGLISH); + boolean match = false; + for(Iterator<String> it = names.iterator(); it.hasNext();) { + // Don't trim the CN, though! + String cn = it.next(); + cn = cn.toLowerCase(Locale.ENGLISH); + // Store CN in StringBuffer in case we need to report an error. + buf.append(" <"); + buf.append(cn); + buf.append('>'); + if(it.hasNext()) { + buf.append(" OR"); + } + + // The CN better have at least two dots if it wants wildcard + // action. It also can't be [*.co.uk] or [*.co.jp] or + // [*.org.uk], etc... + boolean doWildcard = cn.startsWith("*.") && + cn.lastIndexOf('.') >= 0 && + acceptableCountryWildcard(cn) && + !InetAddressUtils.isIPv4Address(host); + + if(doWildcard) { + match = hostName.endsWith(cn.substring(1)); + if(match && strictWithSubDomains) { + // If we're in strict mode, then [*.foo.com] is not + // allowed to match [a.b.foo.com] + match = countDots(hostName) == countDots(cn); + } + } else { + match = hostName.equals(cn); + } + if(match) { + break; + } + } + if(!match) { + throw new SSLException("hostname in certificate didn't match: <" + host + "> !=" + buf); + } + } + + public static boolean acceptableCountryWildcard(String cn) { + int cnLen = cn.length(); + if(cnLen >= 7 && cnLen <= 9) { + // Look for the '.' in the 3rd-last position: + if(cn.charAt(cnLen - 3) == '.') { + // Trim off the [*.] and the [.XX]. + String s = cn.substring(2, cnLen - 3); + // And test against the sorted array of bad 2lds: + int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s); + return x < 0; + } + } + return true; + } + + public static String[] getCNs(X509Certificate cert) { + LinkedList<String> cnList = new LinkedList<String>(); + /* + Sebastian Hauer's original StrictSSLProtocolSocketFactory used + getName() and had the following comment: + + Parses a X.500 distinguished name for the value of the + "Common Name" field. This is done a bit sloppy right + now and should probably be done a bit more according to + <code>RFC 2253</code>. + + I've noticed that toString() seems to do a better job than + getName() on these X500Principal objects, so I'm hoping that + addresses Sebastian's concern. + + For example, getName() gives me this: + 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d + + whereas toString() gives me this: + EMAILADDRESS=juliusdavies@cucbc.com + + Looks like toString() even works with non-ascii domain names! + I tested it with "花子.co.jp" and it worked fine. + */ + String subjectPrincipal = cert.getSubjectX500Principal().toString(); + StringTokenizer st = new StringTokenizer(subjectPrincipal, ","); + while(st.hasMoreTokens()) { + String tok = st.nextToken(); + int x = tok.indexOf("CN="); + if(x >= 0) { + cnList.add(tok.substring(x + 3)); + } + } + if(!cnList.isEmpty()) { + String[] cns = new String[cnList.size()]; + cnList.toArray(cns); + return cns; + } else { + return null; + } + } + + + /** + * Extracts the array of SubjectAlt DNS names from an X509Certificate. + * Returns null if there aren't any. + * <p/> + * Note: Java doesn't appear able to extract international characters + * from the SubjectAlts. It can only extract international characters + * from the CN field. + * <p/> + * (Or maybe the version of OpenSSL I'm using to test isn't storing the + * international characters correctly in the SubjectAlts?). + * + * @param cert X509Certificate + * @return Array of SubjectALT DNS names stored in the certificate. + */ + public static String[] getDNSSubjectAlts(X509Certificate cert) { + LinkedList<String> subjectAltList = new LinkedList<String>(); + Collection<List<?>> c = null; + try { + c = cert.getSubjectAlternativeNames(); + } + catch(CertificateParsingException cpe) { + Logger.getLogger(AbstractVerifier.class.getName()) + .log(Level.FINE, "Error parsing certificate.", cpe); + } + if(c != null) { + for (List<?> aC : c) { + List<?> list = aC; + int type = ((Integer) list.get(0)).intValue(); + // If type is 2, then we've got a dNSName + if (type == 2) { + String s = (String) list.get(1); + subjectAltList.add(s); + } + } + } + if(!subjectAltList.isEmpty()) { + String[] subjectAlts = new String[subjectAltList.size()]; + subjectAltList.toArray(subjectAlts); + return subjectAlts; + } else { + return null; + } + } + + /** + * Counts the number of dots "." in a string. + * @param s string to count dots from + * @return number of dots + */ + public static int countDots(final String s) { + int count = 0; + for(int i = 0; i < s.length(); i++) { + if(s.charAt(i) == '.') { + count++; + } + } + return count; + } + +} diff --git a/src/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java b/src/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java new file mode 100644 index 0000000..05828fb --- /dev/null +++ b/src/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java @@ -0,0 +1,54 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java $ + * $Revision: 617642 $ + * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.ssl; + +/** + * The ALLOW_ALL HostnameVerifier essentially turns hostname verification + * off. This implementation is a no-op, and never throws the SSLException. + * + * @author Julius Davies + */ +public class AllowAllHostnameVerifier extends AbstractVerifier { + + public final void verify( + final String host, + final String[] cns, + final String[] subjectAlts) { + // Allow everything - so never blowup. + } + + @Override + public final String toString() { + return "ALLOW_ALL"; + } + +} diff --git a/src/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java b/src/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java new file mode 100644 index 0000000..f4129d6 --- /dev/null +++ b/src/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java @@ -0,0 +1,62 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java $ + * $Revision: 617642 $ + * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.ssl; + +import javax.net.ssl.SSLException; + +/** + * The HostnameVerifier that works the same way as Curl and Firefox. + * <p/> + * The hostname must match either the first CN, or any of the subject-alts. + * A wildcard can occur in the CN, and in any of the subject-alts. + * <p/> + * The only difference between BROWSER_COMPATIBLE and STRICT is that a wildcard + * (such as "*.foo.com") with BROWSER_COMPATIBLE matches all subdomains, + * including "a.b.foo.com". + * + * @author Julius Davies + */ +public class BrowserCompatHostnameVerifier extends AbstractVerifier { + + public final void verify( + final String host, + final String[] cns, + final String[] subjectAlts) throws SSLException { + verify(host, cns, subjectAlts, false); + } + + @Override + public final String toString() { + return "BROWSER_COMPATIBLE"; + } + +} diff --git a/src/org/apache/http/conn/ssl/SSLSocketFactory.java b/src/org/apache/http/conn/ssl/SSLSocketFactory.java new file mode 100644 index 0000000..1be6c3a --- /dev/null +++ b/src/org/apache/http/conn/ssl/SSLSocketFactory.java @@ -0,0 +1,397 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java $ + * $Revision: 659194 $ + * $Date: 2008-05-22 11:33:47 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.ssl; + +import org.apache.http.conn.scheme.HostNameResolver; +import org.apache.http.conn.scheme.LayeredSocketFactory; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; + +/** + * Layered socket factory for TLS/SSL connections, based on JSSE. + *. + * <p> + * SSLSocketFactory can be used to validate the identity of the HTTPS + * server against a list of trusted certificates and to authenticate to + * the HTTPS server using a private key. + * </p> + * + * <p> + * SSLSocketFactory will enable server authentication when supplied with + * a {@link KeyStore truststore} file containg one or several trusted + * certificates. The client secure socket will reject the connection during + * the SSL session handshake if the target HTTPS server attempts to + * authenticate itself with a non-trusted certificate. + * </p> + * + * <p> + * Use JDK keytool utility to import a trusted certificate and generate a truststore file: + * <pre> + * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore + * </pre> + * </p> + * + * <p> + * SSLSocketFactory will enable client authentication when supplied with + * a {@link KeyStore keystore} file containg a private key/public certificate + * pair. The client secure socket will use the private key to authenticate + * itself to the target HTTPS server during the SSL session handshake if + * requested to do so by the server. + * The target HTTPS server will in its turn verify the certificate presented + * by the client in order to establish client's authenticity + * </p> + * + * <p> + * Use the following sequence of actions to generate a keystore file + * </p> + * <ul> + * <li> + * <p> + * Use JDK keytool utility to generate a new key + * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre> + * For simplicity use the same password for the key as that of the keystore + * </p> + * </li> + * <li> + * <p> + * Issue a certificate signing request (CSR) + * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre> + * </p> + * </li> + * <li> + * <p> + * Send the certificate request to the trusted Certificate Authority for signature. + * One may choose to act as her own CA and sign the certificate request using a PKI + * tool, such as OpenSSL. + * </p> + * </li> + * <li> + * <p> + * Import the trusted CA root certificate + * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre> + * </p> + * </li> + * <li> + * <p> + * Import the PKCS#7 file containg the complete certificate chain + * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre> + * </p> + * </li> + * <li> + * <p> + * Verify the content the resultant keystore file + * <pre>keytool -list -v -keystore my.keystore</pre> + * </p> + * </li> + * </ul> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author Julius Davies + */ + +public class SSLSocketFactory implements LayeredSocketFactory { + + public static final String TLS = "TLS"; + public static final String SSL = "SSL"; + public static final String SSLV2 = "SSLv2"; + + public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER + = new AllowAllHostnameVerifier(); + + public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER + = new BrowserCompatHostnameVerifier(); + + public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER + = new StrictHostnameVerifier(); + /** + * The factory using the default JVM settings for secure connections. + */ + private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory(); + + /** + * Gets an singleton instance of the SSLProtocolSocketFactory. + * @return a SSLProtocolSocketFactory + */ + public static SSLSocketFactory getSocketFactory() { + return DEFAULT_FACTORY; + } + + private final SSLContext sslcontext; + private final javax.net.ssl.SSLSocketFactory socketfactory; + private final HostNameResolver nameResolver; + private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; + + public SSLSocketFactory( + String algorithm, + final KeyStore keystore, + final String keystorePassword, + final KeyStore truststore, + final SecureRandom random, + final HostNameResolver nameResolver) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + super(); + if (algorithm == null) { + algorithm = TLS; + } + KeyManager[] keymanagers = null; + if (keystore != null) { + keymanagers = createKeyManagers(keystore, keystorePassword); + } + TrustManager[] trustmanagers = null; + if (truststore != null) { + trustmanagers = createTrustManagers(truststore); + } + this.sslcontext = SSLContext.getInstance(algorithm); + this.sslcontext.init(keymanagers, trustmanagers, random); + this.socketfactory = this.sslcontext.getSocketFactory(); + this.nameResolver = nameResolver; + } + + public SSLSocketFactory( + final KeyStore keystore, + final String keystorePassword, + final KeyStore truststore) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + this(TLS, keystore, keystorePassword, truststore, null, null); + } + + public SSLSocketFactory(final KeyStore keystore, final String keystorePassword) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + this(TLS, keystore, keystorePassword, null, null, null); + } + + public SSLSocketFactory(final KeyStore truststore) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException + { + this(TLS, null, null, truststore, null, null); + } + + /** + * Constructs an HttpClient SSLSocketFactory backed by the given JSSE + * SSLSocketFactory. + * + * @hide + */ + public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) { + super(); + this.sslcontext = null; + this.socketfactory = socketfactory; + this.nameResolver = null; + } + + /** + * Creates the default SSL socket factory. + * This constructor is used exclusively to instantiate the factory for + * {@link #getSocketFactory getSocketFactory}. + */ + private SSLSocketFactory() { + super(); + this.sslcontext = null; + this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory(); + this.nameResolver = null; + } + + private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { + if (keystore == null) { + throw new IllegalArgumentException("Keystore may not be null"); + } + KeyManagerFactory kmfactory = KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm()); + kmfactory.init(keystore, password != null ? password.toCharArray(): null); + return kmfactory.getKeyManagers(); + } + + private static TrustManager[] createTrustManagers(final KeyStore keystore) + throws KeyStoreException, NoSuchAlgorithmException { + if (keystore == null) { + throw new IllegalArgumentException("Keystore may not be null"); + } + TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmfactory.init(keystore); + return tmfactory.getTrustManagers(); + } + + + // non-javadoc, see interface org.apache.http.conn.SocketFactory + public Socket createSocket() + throws IOException { + + // the cast makes sure that the factory is working as expected + return (SSLSocket) this.socketfactory.createSocket(); + } + + + // non-javadoc, see interface org.apache.http.conn.SocketFactory + public Socket connectSocket( + final Socket sock, + final String host, + final int port, + final InetAddress localAddress, + int localPort, + final HttpParams params + ) throws IOException { + + if (host == null) { + throw new IllegalArgumentException("Target host may not be null."); + } + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null."); + } + + SSLSocket sslsock = (SSLSocket) + ((sock != null) ? sock : createSocket()); + + if ((localAddress != null) || (localPort > 0)) { + + // we need to bind explicitly + if (localPort < 0) + localPort = 0; // indicates "any" + + InetSocketAddress isa = + new InetSocketAddress(localAddress, localPort); + sslsock.bind(isa); + } + + int connTimeout = HttpConnectionParams.getConnectionTimeout(params); + int soTimeout = HttpConnectionParams.getSoTimeout(params); + + InetSocketAddress remoteAddress; + if (this.nameResolver != null) { + remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port); + } else { + remoteAddress = new InetSocketAddress(host, port); + } + + sslsock.connect(remoteAddress, connTimeout); + + sslsock.setSoTimeout(soTimeout); + try { + hostnameVerifier.verify(host, sslsock); + // verifyHostName() didn't blowup - good! + } catch (IOException iox) { + // close the socket before re-throwing the exception + try { sslsock.close(); } catch (Exception x) { /*ignore*/ } + throw iox; + } + + return sslsock; + } + + + /** + * Checks whether a socket connection is secure. + * This factory creates TLS/SSL socket connections + * which, by default, are considered secure. + * <br/> + * Derived classes may override this method to perform + * runtime checks, for example based on the cypher suite. + * + * @param sock the connected socket + * + * @return <code>true</code> + * + * @throws IllegalArgumentException if the argument is invalid + */ + public boolean isSecure(Socket sock) + throws IllegalArgumentException { + + if (sock == null) { + throw new IllegalArgumentException("Socket may not be null."); + } + // This instanceof check is in line with createSocket() above. + if (!(sock instanceof SSLSocket)) { + throw new IllegalArgumentException + ("Socket not created by this factory."); + } + // This check is performed last since it calls the argument object. + if (sock.isClosed()) { + throw new IllegalArgumentException("Socket is closed."); + } + + return true; + + } // isSecure + + + // non-javadoc, see interface LayeredSocketFactory + public Socket createSocket( + final Socket socket, + final String host, + final int port, + final boolean autoClose + ) throws IOException, UnknownHostException { + SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket( + socket, + host, + port, + autoClose + ); + hostnameVerifier.verify(host, sslSocket); + // verifyHostName() didn't blowup - good! + return sslSocket; + } + + public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) { + if ( hostnameVerifier == null ) { + throw new IllegalArgumentException("Hostname verifier may not be null"); + } + this.hostnameVerifier = hostnameVerifier; + } + + public X509HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + +} diff --git a/src/org/apache/http/conn/ssl/StrictHostnameVerifier.java b/src/org/apache/http/conn/ssl/StrictHostnameVerifier.java new file mode 100644 index 0000000..5eb0d96 --- /dev/null +++ b/src/org/apache/http/conn/ssl/StrictHostnameVerifier.java @@ -0,0 +1,69 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java $ + * $Revision: 617642 $ + * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.ssl; + +import javax.net.ssl.SSLException; + +/** + * The Strict HostnameVerifier works the same way as Sun Java 1.4, Sun + * Java 5, Sun Java 6-rc. It's also pretty close to IE6. This + * implementation appears to be compliant with RFC 2818 for dealing with + * wildcards. + * <p/> + * The hostname must match either the first CN, or any of the subject-alts. + * A wildcard can occur in the CN, and in any of the subject-alts. The + * one divergence from IE6 is how we only check the first CN. IE6 allows + * a match against any of the CNs present. We decided to follow in + * Sun Java 1.4's footsteps and only check the first CN. (If you need + * to check all the CN's, feel free to write your own implementation!). + * <p/> + * A wildcard such as "*.foo.com" matches only subdomains in the same + * level, for example "a.foo.com". It does not match deeper subdomains + * such as "a.b.foo.com". + * + * @author Julius Davies + */ +public class StrictHostnameVerifier extends AbstractVerifier { + + public final void verify( + final String host, + final String[] cns, + final String[] subjectAlts) throws SSLException { + verify(host, cns, subjectAlts, true); + } + + @Override + public final String toString() { + return "STRICT"; + } + +} diff --git a/src/org/apache/http/conn/ssl/X509HostnameVerifier.java b/src/org/apache/http/conn/ssl/X509HostnameVerifier.java new file mode 100644 index 0000000..05ad04d --- /dev/null +++ b/src/org/apache/http/conn/ssl/X509HostnameVerifier.java @@ -0,0 +1,86 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/X509HostnameVerifier.java $ + * $Revision: 618365 $ + * $Date: 2008-02-04 10:20:08 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.ssl; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import java.io.IOException; +import java.security.cert.X509Certificate; + +/** + * Interface for checking if a hostname matches the names stored inside the + * server's X.509 certificate. Implements javax.net.ssl.HostnameVerifier, but + * we don't actually use that interface. Instead we added some methods that + * take String parameters (instead of javax.net.ssl.HostnameVerifier's + * SSLSession). JUnit is a lot easier this way! :-) + * <p/> + * We provide the HostnameVerifier.DEFAULT, HostnameVerifier.STRICT, and + * HostnameVerifier.ALLOW_ALL implementations. But feel free to define + * your own implementation! + * <p/> + * Inspired by Sebastian Hauer's original StrictSSLProtocolSocketFactory in the + * HttpClient "contrib" repository. + * + * @author Julius Davies + * @author <a href="mailto:hauer@psicode.com">Sebastian Hauer</a> + * + * @since 4.0 (8-Dec-2006) + */ +public interface X509HostnameVerifier extends HostnameVerifier { + + boolean verify(String host, SSLSession session); + + void verify(String host, SSLSocket ssl) throws IOException; + + void verify(String host, X509Certificate cert) throws SSLException; + + /** + * Checks to see if the supplied hostname matches any of the supplied CNs + * or "DNS" Subject-Alts. Most implementations only look at the first CN, + * and ignore any additional CNs. Most implementations do look at all of + * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards + * according to RFC 2818. + * + * @param cns CN fields, in order, as extracted from the X.509 + * certificate. + * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted + * from the X.509 certificate. + * @param host The hostname to verify. + * @throws SSLException If verification failed. + */ + void verify(String host, String[] cns, String[] subjectAlts) + throws SSLException; + + +} diff --git a/src/org/apache/http/conn/ssl/package.html b/src/org/apache/http/conn/ssl/package.html new file mode 100644 index 0000000..a5c737f --- /dev/null +++ b/src/org/apache/http/conn/ssl/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +TLS/SSL specific parts of the <i>HttpConn</i> API. + +</body> +</html> diff --git a/src/org/apache/http/conn/util/InetAddressUtils.java b/src/org/apache/http/conn/util/InetAddressUtils.java new file mode 100644 index 0000000..71f2190 --- /dev/null +++ b/src/org/apache/http/conn/util/InetAddressUtils.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/util/InetAddressUtils.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.conn.util; + +import java.util.regex.Pattern; + +/** + * A collection of utilities relating to InetAddresses. + */ +public class InetAddressUtils { + + private InetAddressUtils() { + } + + private static final Pattern IPV4_PATTERN = + Pattern.compile( + "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$"); + + private static final Pattern IPV6_STD_PATTERN = + Pattern.compile( + "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"); + + private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = + Pattern.compile( + "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"); + + public static boolean isIPv4Address(final String input) { + return IPV4_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6StdAddress(final String input) { + return IPV6_STD_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6HexCompressedAddress(final String input) { + return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6Address(final String input) { + return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); + } + +} diff --git a/src/org/apache/http/cookie/ClientCookie.java b/src/org/apache/http/cookie/ClientCookie.java new file mode 100644 index 0000000..96edec9 --- /dev/null +++ b/src/org/apache/http/cookie/ClientCookie.java @@ -0,0 +1,67 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/ClientCookie.java $ + * $Revision: 578403 $ + * $Date: 2007-09-22 03:56:04 -0700 (Sat, 22 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +/** + * ClientCookie extends the standard {@link Cookie} interface with + * additional client specific functionality such ability to retrieve + * original cookie attributes exactly as they were specified by the + * origin server. This is important for generating the <tt>Cookie</tt> + * header because some cookie specifications require that the + * <tt>Cookie</tt> header should include certain attributes only if + * they were specified in the <tt>Set-Cookie</tt> header. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface ClientCookie extends Cookie { + + // RFC2109 attributes + public static final String VERSION_ATTR = "version"; + public static final String PATH_ATTR = "path"; + public static final String DOMAIN_ATTR = "domain"; + public static final String MAX_AGE_ATTR = "max-age"; + public static final String SECURE_ATTR = "secure"; + public static final String COMMENT_ATTR = "comment"; + public static final String EXPIRES_ATTR = "expires"; + + // RFC2965 attributes + public static final String PORT_ATTR = "port"; + public static final String COMMENTURL_ATTR = "commenturl"; + public static final String DISCARD_ATTR = "discard"; + + String getAttribute(String name); + + boolean containsAttribute(String name); + +}
\ No newline at end of file diff --git a/src/org/apache/http/cookie/Cookie.java b/src/org/apache/http/cookie/Cookie.java new file mode 100644 index 0000000..5eae9d5 --- /dev/null +++ b/src/org/apache/http/cookie/Cookie.java @@ -0,0 +1,139 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/Cookie.java $ + * $Revision: 578403 $ + * $Date: 2007-09-22 03:56:04 -0700 (Sat, 22 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import java.util.Date; + +/** + * HTTP "magic-cookie" represents a piece of state information + * that the HTTP agent and the target server can exchange to maintain + * a session. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface Cookie { + + /** + * Returns the name. + * + * @return String name The name + */ + String getName(); + + /** + * Returns the value. + * + * @return String value The current value. + */ + String getValue(); + + /** + * Returns the comment describing the purpose of this cookie, or + * <tt>null</tt> if no such comment has been defined. + * + * @return comment + */ + String getComment(); + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described by the information at this URL. + */ + String getCommentURL(); + + /** + * Returns the expiration {@link Date} of the cookie, or <tt>null</tt> + * if none exists. + * <p><strong>Note:</strong> the object returned by this method is + * considered immutable. Changing it (e.g. using setTime()) could result + * in undefined behaviour. Do so at your peril. </p> + * @return Expiration {@link Date}, or <tt>null</tt>. + */ + Date getExpiryDate(); + + /** + * Returns <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise. + * + * @return <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise + */ + boolean isPersistent(); + + /** + * Returns domain attribute of the cookie. + * + * @return the value of the domain attribute + */ + String getDomain(); + + /** + * Returns the path attribute of the cookie + * + * @return The value of the path attribute. + */ + String getPath(); + + /** + * Get the Port attribute. It restricts the ports to which a cookie + * may be returned in a Cookie request header. + */ + int[] getPorts(); + + /** + * Indicates whether this cookie requires a secure connection. + * + * @return <code>true</code> if this cookie should only be sent + * over secure connections, <code>false</code> otherwise. + */ + boolean isSecure(); + + /** + * Returns the version of the cookie specification to which this + * cookie conforms. + * + * @return the version of the cookie. + */ + int getVersion(); + + /** + * Returns true if this cookie has expired. + * @param date Current time + * + * @return <tt>true</tt> if the cookie has expired. + */ + boolean isExpired(final Date date); + +} + diff --git a/src/org/apache/http/cookie/CookieAttributeHandler.java b/src/org/apache/http/cookie/CookieAttributeHandler.java new file mode 100644 index 0000000..a79d115 --- /dev/null +++ b/src/org/apache/http/cookie/CookieAttributeHandler.java @@ -0,0 +1,78 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/CookieAttributeHandler.java $ + * $Revision: 558519 $ + * $Date: 2007-07-22 11:19:49 -0700 (Sun, 22 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.cookie; + +/** + * Ths interface represents a cookie attribute handler responsible + * for parsing, validating, and matching a specific cookie attribute, + * such as path, domain, port, etc. + * + * Different cookie specifications can provide a specific + * implementation for this class based on their cookie handling + * rules. + * + * @author jain.samit@gmail.com (Samit Jain) + * + * @since 4.0 + */ +public interface CookieAttributeHandler { + + /** + * Parse the given cookie attribute value and update the corresponding + * {@link org.apache.http.cookie.Cookie} property. + * + * @param cookie {@link org.apache.http.cookie.Cookie} to be updated + * @param value cookie attribute value from the cookie response header + */ + void parse(SetCookie cookie, String value) + throws MalformedCookieException; + + /** + * Peforms cookie validation for the given attribute value. + * + * @param cookie {@link org.apache.http.cookie.Cookie} to validate + * @param origin the cookie source to validate against + * @throws MalformedCookieException if cookie validation fails for this attribute + */ + void validate(Cookie cookie, CookieOrigin origin) + throws MalformedCookieException; + + /** + * Matches the given value (property of the destination host where request is being + * submitted) with the corresponding cookie attribute. + * + * @param cookie {@link org.apache.http.cookie.Cookie} to match + * @param origin the cookie source to match against + * @return <tt>true</tt> if the match is successful; <tt>false</tt> otherwise + */ + boolean match(Cookie cookie, CookieOrigin origin); + +} diff --git a/src/org/apache/http/cookie/CookieIdentityComparator.java b/src/org/apache/http/cookie/CookieIdentityComparator.java new file mode 100644 index 0000000..4fc701c --- /dev/null +++ b/src/org/apache/http/cookie/CookieIdentityComparator.java @@ -0,0 +1,68 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/CookieIdentityComparator.java $ + * $Revision: 618308 $ + * $Date: 2008-02-04 07:51:19 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * This cookie comparator can be used to compare identity of cookies. + * + * <p> + * Cookies are considered identical if their names are equal and + * their domain attributes match ignoring case. + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class CookieIdentityComparator implements Serializable, Comparator<Cookie> { + + private static final long serialVersionUID = 4466565437490631532L; + + public int compare(final Cookie c1, final Cookie c2) { + int res = c1.getName().compareTo(c2.getName()); + if (res == 0) { + // do not differentiate empty and null domains + String d1 = c1.getDomain(); + if (d1 == null) { + d1 = ""; + } + String d2 = c2.getDomain(); + if (d2 == null) { + d2 = ""; + } + res = d1.compareToIgnoreCase(d2); + } + return res; + } + +} diff --git a/src/org/apache/http/cookie/CookieOrigin.java b/src/org/apache/http/cookie/CookieOrigin.java new file mode 100644 index 0000000..ad0448a --- /dev/null +++ b/src/org/apache/http/cookie/CookieOrigin.java @@ -0,0 +1,108 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/CookieOrigin.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.cookie; + +import java.util.Locale; + +/** + * CookieOrigin class incapsulates details of an origin server that + * are relevant when parsing, validating or matching HTTP cookies. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public final class CookieOrigin { + + private final String host; + private final int port; + private final String path; + private final boolean secure; + + public CookieOrigin(final String host, int port, final String path, boolean secure) { + super(); + if (host == null) { + throw new IllegalArgumentException( + "Host of origin may not be null"); + } + if (host.trim().length() == 0) { + throw new IllegalArgumentException( + "Host of origin may not be blank"); + } + if (port < 0) { + throw new IllegalArgumentException("Invalid port: " + port); + } + if (path == null) { + throw new IllegalArgumentException( + "Path of origin may not be null."); + } + this.host = host.toLowerCase(Locale.ENGLISH); + this.port = port; + if (path.trim().length() != 0) { + this.path = path; + } else { + this.path = "/"; + } + this.secure = secure; + } + + public String getHost() { + return this.host; + } + + public String getPath() { + return this.path; + } + + public int getPort() { + return this.port; + } + + public boolean isSecure() { + return this.secure; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append('['); + if (this.secure) { + buffer.append("(secure)"); + } + buffer.append(this.host); + buffer.append(':'); + buffer.append(Integer.toString(this.port)); + buffer.append(this.path); + buffer.append(']'); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/cookie/CookiePathComparator.java b/src/org/apache/http/cookie/CookiePathComparator.java new file mode 100644 index 0000000..f5f0a66 --- /dev/null +++ b/src/org/apache/http/cookie/CookiePathComparator.java @@ -0,0 +1,81 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/CookiePathComparator.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * This cookie comparator ensures that multiple cookies satisfying + * a common criteria are ordered in the <tt>Cookie</tt> header such + * that those with more specific Path attributes precede those with + * less specific. + * + * <p> + * This comparator assumes that Path attributes of two cookies + * path-match a commmon request-URI. Otherwise, the result of the + * comparison is undefined. + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class CookiePathComparator implements Serializable, Comparator<Cookie> { + + private static final long serialVersionUID = 7523645369616405818L; + + private String normalizePath(final Cookie cookie) { + String path = cookie.getPath(); + if (path == null) { + path = "/"; + } + if (!path.endsWith("/")) { + path = path + '/'; + } + return path; + } + + public int compare(final Cookie c1, final Cookie c2) { + String path1 = normalizePath(c1); + String path2 = normalizePath(c2); + if (path1.equals(path2)) { + return 0; + } else if (path1.startsWith(path2)) { + return -1; + } else if (path2.startsWith(path1)) { + return 1; + } else { + // Does not really matter + return 0; + } + } + +} diff --git a/src/org/apache/http/cookie/CookieSpec.java b/src/org/apache/http/cookie/CookieSpec.java new file mode 100644 index 0000000..1eb9f26 --- /dev/null +++ b/src/org/apache/http/cookie/CookieSpec.java @@ -0,0 +1,115 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/CookieSpec.java $ + * $Revision: 603563 $ + * $Date: 2007-12-12 03:17:55 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import java.util.List; + +import org.apache.http.Header; + +/** + * Defines the cookie management specification. + * <p>Cookie management specification must define + * <ul> + * <li> rules of parsing "Set-Cookie" header + * <li> rules of validation of parsed cookies + * <li> formatting of "Cookie" header + * </ul> + * for a given host, port and path of origin + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * + * @since 4.0 + */ +public interface CookieSpec { + + /** + * Returns version of the state management this cookie specification + * conforms to. + * + * @return version of the state management specification + */ + int getVersion(); + + /** + * Parse the <tt>"Set-Cookie"</tt> Header into an array of Cookies. + * + * <p>This method will not perform the validation of the resultant + * {@link Cookie}s</p> + * + * @see #validate + * + * @param header the <tt>Set-Cookie</tt> received from the server + * @param origin details of the cookie origin + * @return an array of <tt>Cookie</tt>s parsed from the header + * @throws MalformedCookieException if an exception occurs during parsing + */ + List<Cookie> parse(Header header, CookieOrigin origin) throws MalformedCookieException; + + /** + * Validate the cookie according to validation rules defined by the + * cookie specification. + * + * @param cookie the Cookie to validate + * @param origin details of the cookie origin + * @throws MalformedCookieException if the cookie is invalid + */ + void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException; + + /** + * Determines if a Cookie matches the target location. + * + * @param cookie the Cookie to be matched + * @param origin the target to test against + * + * @return <tt>true</tt> if the cookie should be submitted with a request + * with given attributes, <tt>false</tt> otherwise. + */ + boolean match(Cookie cookie, CookieOrigin origin); + + /** + * Create <tt>"Cookie"</tt> headers for an array of Cookies. + * + * @param cookies the Cookies format into a Cookie header + * @return a Header for the given Cookies. + * @throws IllegalArgumentException if an input parameter is illegal + */ + List<Header> formatCookies(List<Cookie> cookies); + + /** + * Returns a request header identifying what version of the state management + * specification is understood. May be <code>null</code> if the cookie + * specification does not support <tt>Cookie2</tt> header. + */ + Header getVersionHeader(); + +} diff --git a/src/org/apache/http/cookie/CookieSpecFactory.java b/src/org/apache/http/cookie/CookieSpecFactory.java new file mode 100644 index 0000000..9d5c21d --- /dev/null +++ b/src/org/apache/http/cookie/CookieSpecFactory.java @@ -0,0 +1,46 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/CookieSpecFactory.java $ + * $Revision: 489636 $ + * $Date: 2006-12-22 04:34:57 -0800 (Fri, 22 Dec 2006) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface CookieSpecFactory { + + CookieSpec newInstance(HttpParams params); + +} diff --git a/src/org/apache/http/cookie/CookieSpecRegistry.java b/src/org/apache/http/cookie/CookieSpecRegistry.java new file mode 100644 index 0000000..64b9c8b --- /dev/null +++ b/src/org/apache/http/cookie/CookieSpecRegistry.java @@ -0,0 +1,160 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/CookieSpecRegistry.java $ + * $Revision: 652950 $ + * $Date: 2008-05-02 16:49:48 -0700 (Fri, 02 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.params.HttpParams; + +/** + * Cookie specification registry that can be used to obtain the corresponding + * cookie specification implementation for a given type of type or version of + * cookie. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @since 4.0 + */ +public final class CookieSpecRegistry { + + private final Map<String,CookieSpecFactory> registeredSpecs; + + public CookieSpecRegistry() { + super(); + this.registeredSpecs = new LinkedHashMap<String,CookieSpecFactory>(); + } + + /** + * Registers a {@link CookieSpecFactory} with the given identifier. + * If a specification with the given name already exists it will be overridden. + * This nameis the same one used to retrieve the {@link CookieSpecFactory} + * from {@link #getCookieSpec(String)}. + * + * @param name the identifier for this specification + * @param factory the {@link CookieSpecFactory} class to register + * + * @see #getCookieSpec(String) + */ + public synchronized void register(final String name, final CookieSpecFactory factory) { + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + if (factory == null) { + throw new IllegalArgumentException("Cookie spec factory may not be null"); + } + registeredSpecs.put(name.toLowerCase(Locale.ENGLISH), factory); + } + + /** + * Unregisters the {@link CookieSpecFactory} with the given ID. + * + * @param id the identifier of the {@link CookieSpec cookie specification} to unregister + */ + public synchronized void unregister(final String id) { + if (id == null) { + throw new IllegalArgumentException("Id may not be null"); + } + registeredSpecs.remove(id.toLowerCase(Locale.ENGLISH)); + } + + /** + * Gets the {@link CookieSpec cookie specification} with the given ID. + * + * @param name the {@link CookieSpec cookie specification} identifier + * @param params the {@link HttpParams HTTP parameters} for the cookie + * specification. + * + * @return {@link CookieSpec cookie specification} + * + * @throws IllegalStateException if a policy with the given name cannot be found + */ + public synchronized CookieSpec getCookieSpec(final String name, final HttpParams params) + throws IllegalStateException { + + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + CookieSpecFactory factory = registeredSpecs.get(name.toLowerCase(Locale.ENGLISH)); + if (factory != null) { + return factory.newInstance(params); + } else { + throw new IllegalStateException("Unsupported cookie spec: " + name); + } + } + + /** + * Gets the {@link CookieSpec cookie specification} with the given name. + * + * @param name the {@link CookieSpec cookie specification} identifier + * + * @return {@link CookieSpec cookie specification} + * + * @throws IllegalStateException if a policy with the given name cannot be found + */ + public synchronized CookieSpec getCookieSpec(final String name) + throws IllegalStateException { + return getCookieSpec(name, null); + } + + /** + * Obtains a list containing names of all registered {@link CookieSpec cookie + * specs} in their default order. + * + * Note that the DEFAULT policy (if present) is likely to be the same + * as one of the other policies, but does not have to be. + * + * @return list of registered cookie spec names + */ + public synchronized List<String> getSpecNames(){ + return new ArrayList<String>(registeredSpecs.keySet()); + } + + /** + * Populates the internal collection of registered {@link CookieSpec cookie + * specs} with the content of the map passed as a parameter. + * + * @param map cookie specs + */ + public synchronized void setItems(final Map<String, CookieSpecFactory> map) { + if (map == null) { + return; + } + registeredSpecs.clear(); + registeredSpecs.putAll(map); + } + +} diff --git a/src/org/apache/http/cookie/MalformedCookieException.java b/src/org/apache/http/cookie/MalformedCookieException.java new file mode 100644 index 0000000..e3f30a9 --- /dev/null +++ b/src/org/apache/http/cookie/MalformedCookieException.java @@ -0,0 +1,74 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/MalformedCookieException.java $ + * $Revision: 508891 $ + * $Date: 2007-02-18 02:08:48 -0800 (Sun, 18 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import org.apache.http.ProtocolException; + +/** + * Signals that a cookie is in some way invalid or illegal in a given + * context + * + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class MalformedCookieException extends ProtocolException { + + private static final long serialVersionUID = -6695462944287282185L; + + /** + * Creates a new MalformedCookieException with a <tt>null</tt> detail message. + */ + public MalformedCookieException() { + super(); + } + + /** + * Creates a new MalformedCookieException with a specified message string. + * + * @param message The exception detail message + */ + public MalformedCookieException(String message) { + super(message); + } + + /** + * Creates a new MalformedCookieException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public MalformedCookieException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/cookie/SM.java b/src/org/apache/http/cookie/SM.java new file mode 100644 index 0000000..a7047d5 --- /dev/null +++ b/src/org/apache/http/cookie/SM.java @@ -0,0 +1,48 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/SM.java $ + * $Revision: 582602 $ + * $Date: 2007-10-07 02:35:48 -0700 (Sun, 07 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +/** + * Constants and static helpers related to the HTTP state management. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface SM { + + public static final String COOKIE = "Cookie"; + public static final String COOKIE2 = "Cookie2"; + public static final String SET_COOKIE = "Set-Cookie"; + public static final String SET_COOKIE2 = "Set-Cookie2"; + +} diff --git a/src/org/apache/http/cookie/SetCookie.java b/src/org/apache/http/cookie/SetCookie.java new file mode 100644 index 0000000..d207c48 --- /dev/null +++ b/src/org/apache/http/cookie/SetCookie.java @@ -0,0 +1,115 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/SetCookie.java $ + * $Revision: 617193 $ + * $Date: 2008-01-31 11:26:47 -0800 (Thu, 31 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +import java.util.Date; + +/** + * This interface represents a <code>SetCookie</code> response header sent by the + * origin server to the HTTP agent in order to maintain a conversational state. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface SetCookie extends Cookie { + + void setValue(String value); + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described using this comment. + * + * @param comment + * + * @see #getComment() + */ + void setComment(String comment); + + /** + * Sets expiration date. + * <p><strong>Note:</strong> the object returned by this method is considered + * immutable. Changing it (e.g. using setTime()) could result in undefined + * behaviour. Do so at your peril.</p> + * + * @param expiryDate the {@link Date} after which this cookie is no longer valid. + * + * @see Cookie#getExpiryDate + * + */ + void setExpiryDate (Date expiryDate); + + /** + * Sets the domain attribute. + * + * @param domain The value of the domain attribute + * + * @see Cookie#getDomain + */ + void setDomain(String domain); + + /** + * Sets the path attribute. + * + * @param path The value of the path attribute + * + * @see Cookie#getPath + * + */ + void setPath(String path); + + /** + * Sets the secure attribute of the cookie. + * <p> + * When <tt>true</tt> the cookie should only be sent + * using a secure protocol (https). This should only be set when + * the cookie's originating server used a secure protocol to set the + * cookie's value. + * + * @param secure The value of the secure attribute + * + * @see #isSecure() + */ + void setSecure (boolean secure); + + /** + * Sets the version of the cookie specification to which this + * cookie conforms. + * + * @param version the version of the cookie. + * + * @see Cookie#getVersion + */ + void setVersion(int version); + +} + diff --git a/src/org/apache/http/cookie/SetCookie2.java b/src/org/apache/http/cookie/SetCookie2.java new file mode 100644 index 0000000..cd0420e --- /dev/null +++ b/src/org/apache/http/cookie/SetCookie2.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/SetCookie2.java $ + * $Revision: 578408 $ + * $Date: 2007-09-22 04:53:57 -0700 (Sat, 22 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie; + +/** + * This interface represents a <code>SetCookie2</code> response header sent by the + * origin server to the HTTP agent in order to maintain a conversational state. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public interface SetCookie2 extends SetCookie { + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described by the information at this URL. + */ + void setCommentURL(String commentURL); + + /** + * Sets the Port attribute. It restricts the ports to which a cookie + * may be returned in a Cookie request header. + */ + void setPorts(int[] ports); + + /** + * Set the Discard attribute. + * + * Note: <tt>Discard</tt> attribute overrides <tt>Max-age</tt>. + * + * @see #isPersistent() + */ + void setDiscard(boolean discard); + +} + diff --git a/src/org/apache/http/cookie/package.html b/src/org/apache/http/cookie/package.html new file mode 100644 index 0000000..891d9c3 --- /dev/null +++ b/src/org/apache/http/cookie/package.html @@ -0,0 +1,41 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The API for client-side state management via cookies, +commonly referred to as <i>HttpCookie</i>. + +</body> +</html> diff --git a/src/org/apache/http/cookie/params/CookieSpecPNames.java b/src/org/apache/http/cookie/params/CookieSpecPNames.java new file mode 100644 index 0000000..6a6f6d0 --- /dev/null +++ b/src/org/apache/http/cookie/params/CookieSpecPNames.java @@ -0,0 +1,68 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/params/CookieSpecPNames.java $ + * $Revision: 578403 $ + * $Date: 2007-09-22 03:56:04 -0700 (Sat, 22 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie.params; + +/** + * Parameter names for cookie specifications in HttpCookie. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 578403 $ + * + * @since 4.0 + */ +public interface CookieSpecPNames { + + /** + * Parameter for the date patterns used for parsing. + * <p> + * This parameter expects a value of type {@link java.util.Collection}. + * The collection elements are of type {@link String} + * and must be compatible with the syntax of + * {@link java.text.SimpleDateFormat}. + * </p> + */ + public static final String DATE_PATTERNS = "http.protocol.cookie-datepatterns"; + + /** + * Parameter for Cookie header formatting. + * Defines whether {@link org.apache.http.cookie.Cookie cookies} + * should be put on + * a single {@link org.apache.http.Header request header}. + * If not, each cookie is formatted in a seperate Cookie header. + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + */ + public static final String SINGLE_COOKIE_HEADER = "http.protocol.single-cookie-header"; + +} diff --git a/src/org/apache/http/cookie/params/CookieSpecParamBean.java b/src/org/apache/http/cookie/params/CookieSpecParamBean.java new file mode 100644 index 0000000..6016022 --- /dev/null +++ b/src/org/apache/http/cookie/params/CookieSpecParamBean.java @@ -0,0 +1,53 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/params/CookieSpecParamBean.java $ + * $Revision: 632313 $ + * $Date: 2008-02-29 05:19:50 -0800 (Fri, 29 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.cookie.params; + +import java.util.Collection; + +import org.apache.http.params.HttpAbstractParamBean; +import org.apache.http.params.HttpParams; + +public class CookieSpecParamBean extends HttpAbstractParamBean { + + public CookieSpecParamBean (final HttpParams params) { + super(params); + } + + public void setDatePatterns (final Collection <String> patterns) { + params.setParameter(CookieSpecPNames.DATE_PATTERNS, patterns); + } + + public void setSingleHeader (final boolean singleHeader) { + params.setBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, singleHeader); + } + +} diff --git a/src/org/apache/http/cookie/params/package.html b/src/org/apache/http/cookie/params/package.html new file mode 100644 index 0000000..e6fb7cd --- /dev/null +++ b/src/org/apache/http/cookie/params/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/cookie/params/package.html $ + * $Revision: 555193 $ + * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Parameters for configuring <i>HttpCookie</i>. + +</body> +</html> diff --git a/src/org/apache/http/entity/AbstractHttpEntity.java b/src/org/apache/http/entity/AbstractHttpEntity.java new file mode 100644 index 0000000..0fce6eb --- /dev/null +++ b/src/org/apache/http/entity/AbstractHttpEntity.java @@ -0,0 +1,213 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/AbstractHttpEntity.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.IOException; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.message.BasicHeader; +import org.apache.http.protocol.HTTP; + +/** + * Abstract base class for entities. + * Provides the commonly used attributes for streamed and self-contained + * implementations of {@link HttpEntity HttpEntity}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpEntity implements HttpEntity { + + /** + * The Content-Type header. + * Returned by {@link #getContentType getContentType}, + * unless that method is overridden. + */ + protected Header contentType; + + /** + * The Content-Encoding header. + * Returned by {@link #getContentEncoding getContentEncoding}, + * unless that method is overridden. + */ + protected Header contentEncoding; + + /** + * The 'chunked' flag. + * Returned by {@link #isChunked isChunked}, + * unless that method is overridden. + */ + protected boolean chunked; + + + /** + * Protected default constructor. + * The attributes of the created object remain + * <code>null</code> and <code>false</code>, respectively. + */ + protected AbstractHttpEntity() { + super(); + } + + + /** + * Obtains the Content-Type header. + * The default implementation returns the value of the + * {@link #contentType contentType} attribute. + * + * @return the Content-Type header, or <code>null</code> + */ + public Header getContentType() { + return this.contentType; + } + + + /** + * Obtains the Content-Encoding header. + * The default implementation returns the value of the + * {@link #contentEncoding contentEncoding} attribute. + * + * @return the Content-Encoding header, or <code>null</code> + */ + public Header getContentEncoding() { + return this.contentEncoding; + } + + /** + * Obtains the 'chunked' flag. + * The default implementation returns the value of the + * {@link #chunked chunked} attribute. + * + * @return the 'chunked' flag + */ + public boolean isChunked() { + return this.chunked; + } + + + /** + * Specifies the Content-Type header. + * The default implementation sets the value of the + * {@link #contentType contentType} attribute. + * + * @param contentType the new Content-Encoding header, or + * <code>null</code> to unset + */ + public void setContentType(final Header contentType) { + this.contentType = contentType; + } + + /** + * Specifies the Content-Type header, as a string. + * The default implementation calls + * {@link #setContentType(Header) setContentType(Header)}. + * + * @param ctString the new Content-Type header, or + * <code>null</code> to unset + */ + public void setContentType(final String ctString) { + Header h = null; + if (ctString != null) { + h = new BasicHeader(HTTP.CONTENT_TYPE, ctString); + } + setContentType(h); + } + + + /** + * Specifies the Content-Encoding header. + * The default implementation sets the value of the + * {@link #contentEncoding contentEncoding} attribute. + * + * @param contentEncoding the new Content-Encoding header, or + * <code>null</code> to unset + */ + public void setContentEncoding(final Header contentEncoding) { + this.contentEncoding = contentEncoding; + } + + /** + * Specifies the Content-Encoding header, as a string. + * The default implementation calls + * {@link #setContentEncoding(Header) setContentEncoding(Header)}. + * + * @param ceString the new Content-Encoding header, or + * <code>null</code> to unset + */ + public void setContentEncoding(final String ceString) { + Header h = null; + if (ceString != null) { + h = new BasicHeader(HTTP.CONTENT_ENCODING, ceString); + } + setContentEncoding(h); + } + + + /** + * Specifies the 'chunked' flag. + * The default implementation sets the value of the + * {@link #chunked chunked} attribute. + * + * @param b the new 'chunked' flag + */ + public void setChunked(boolean b) { + this.chunked = b; + } + + + /** + * Does not consume anything. + * The default implementation does nothing if + * {@link HttpEntity#isStreaming isStreaming} + * returns <code>false</code>, and throws an exception + * if it returns <code>true</code>. + * This removes the burden of implementing + * an empty method for non-streaming entities. + * + * @throws IOException in case of an I/O problem + * @throws UnsupportedOperationException + * if a streaming subclass does not override this method + */ + public void consumeContent() + throws IOException, UnsupportedOperationException{ + if (isStreaming()) { + throw new UnsupportedOperationException + ("streaming entity does not implement consumeContent()"); + } + } // consumeContent + + +} // class AbstractHttpEntity diff --git a/src/org/apache/http/entity/BasicHttpEntity.java b/src/org/apache/http/entity/BasicHttpEntity.java new file mode 100644 index 0000000..df3c07c --- /dev/null +++ b/src/org/apache/http/entity/BasicHttpEntity.java @@ -0,0 +1,146 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/BasicHttpEntity.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A generic streamed entity being received on a connection. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public class BasicHttpEntity extends AbstractHttpEntity { + + private InputStream content; + private boolean contentObtained; + private long length; + + /** + * Creates a new basic entity. + * The content is initially missing, the content length + * is set to a negative number. + */ + public BasicHttpEntity() { + super(); + this.length = -1; + } + + // non-javadoc, see interface HttpEntity + public long getContentLength() { + return this.length; + } + + /** + * Obtains the content, once only. + * + * @return the content, if this is the first call to this method + * since {@link #setContent setContent} has been called + * + * @throws IllegalStateException + * if the content has been obtained before, or + * has not yet been provided + */ + public InputStream getContent() + throws IllegalStateException { + if (this.content == null) { + throw new IllegalStateException("Content has not been provided"); + } + if (this.contentObtained) { + throw new IllegalStateException("Content has been consumed"); + } + this.contentObtained = true; + return this.content; + + } // getContent + + /** + * Tells that this entity is not repeatable. + * + * @return <code>false</code> + */ + public boolean isRepeatable() { + return false; + } + + /** + * Specifies the length of the content. + * + * @param len the number of bytes in the content, or + * a negative number to indicate an unknown length + */ + public void setContentLength(long len) { + this.length = len; + } + + /** + * Specifies the content. + * + * @param instream the stream to return with the next call to + * {@link #getContent getContent} + */ + public void setContent(final InputStream instream) { + this.content = instream; + this.contentObtained = false; + } + + // non-javadoc, see interface HttpEntity + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + InputStream instream = getContent(); + int l; + byte[] tmp = new byte[2048]; + while ((l = instream.read(tmp)) != -1) { + outstream.write(tmp, 0, l); + } + } + + // non-javadoc, see interface HttpEntity + public boolean isStreaming() { + return !this.contentObtained && this.content != null; + } + + // non-javadoc, see interface HttpEntity + public void consumeContent() throws IOException { + if (content != null) { + content.close(); // reads to the end of the entity + } + } + +} // class BasicHttpEntity diff --git a/src/org/apache/http/entity/BufferedHttpEntity.java b/src/org/apache/http/entity/BufferedHttpEntity.java new file mode 100644 index 0000000..9888797 --- /dev/null +++ b/src/org/apache/http/entity/BufferedHttpEntity.java @@ -0,0 +1,120 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/BufferedHttpEntity.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.util.EntityUtils; + +/** + * A wrapping entity that buffers it content if necessary. + * The buffered entity is always repeatable. + * If the wrapped entity is repeatable itself, calls are passed through. + * If the wrapped entity is not repeatable, the content is read into a + * buffer once and provided from there as often as required. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public class BufferedHttpEntity extends HttpEntityWrapper { + + private final byte[] buffer; + + public BufferedHttpEntity(final HttpEntity entity) throws IOException { + super(entity); + if (!entity.isRepeatable() || entity.getContentLength() < 0) { + this.buffer = EntityUtils.toByteArray(entity); + } else { + this.buffer = null; + } + } + + public long getContentLength() { + if (this.buffer != null) { + return this.buffer.length; + } else { + return wrappedEntity.getContentLength(); + } + } + + public InputStream getContent() throws IOException { + if (this.buffer != null) { + return new ByteArrayInputStream(this.buffer); + } else { + return wrappedEntity.getContent(); + } + } + + /** + * Tells that this entity does not have to be chunked. + * + * @return <code>false</code> + */ + public boolean isChunked() { + return (buffer == null) && wrappedEntity.isChunked(); + } + + /** + * Tells that this entity is repeatable. + * + * @return <code>true</code> + */ + public boolean isRepeatable() { + return true; + } + + + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + if (this.buffer != null) { + outstream.write(this.buffer); + } else { + wrappedEntity.writeTo(outstream); + } + } + + + // non-javadoc, see interface HttpEntity + public boolean isStreaming() { + return (buffer == null) && wrappedEntity.isStreaming(); + } + +} // class BufferedHttpEntity diff --git a/src/org/apache/http/entity/ByteArrayEntity.java b/src/org/apache/http/entity/ByteArrayEntity.java new file mode 100644 index 0000000..c7257f7 --- /dev/null +++ b/src/org/apache/http/entity/ByteArrayEntity.java @@ -0,0 +1,94 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/ByteArrayEntity.java $ + * $Revision: 604625 $ + * $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An entity whose content is retrieved from a byte array. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 604625 $ + * + * @since 4.0 + */ +public class ByteArrayEntity extends AbstractHttpEntity implements Cloneable { + + protected final byte[] content; + + public ByteArrayEntity(final byte[] b) { + super(); + if (b == null) { + throw new IllegalArgumentException("Source byte array may not be null"); + } + this.content = b; + } + + public boolean isRepeatable() { + return true; + } + + public long getContentLength() { + return this.content.length; + } + + public InputStream getContent() { + return new ByteArrayInputStream(this.content); + } + + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + outstream.write(this.content); + outstream.flush(); + } + + + /** + * Tells that this entity is not streaming. + * + * @return <code>false</code> + */ + public boolean isStreaming() { + return false; + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} // class ByteArrayEntity diff --git a/src/org/apache/http/entity/ContentLengthStrategy.java b/src/org/apache/http/entity/ContentLengthStrategy.java new file mode 100644 index 0000000..cc4ab7d --- /dev/null +++ b/src/org/apache/http/entity/ContentLengthStrategy.java @@ -0,0 +1,54 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/ContentLengthStrategy.java $ + * $Revision: 613298 $ + * $Date: 2008-01-18 14:09:22 -0800 (Fri, 18 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; + +/** + * Represents a strategy to determine the content length based on the properties + * of an HTTP message. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 613298 $ + * + * @since 4.0 + */ +public interface ContentLengthStrategy { + + public static final int IDENTITY = -1; + public static final int CHUNKED = -2; + + long determineLength(HttpMessage message) throws HttpException; + +} diff --git a/src/org/apache/http/entity/ContentProducer.java b/src/org/apache/http/entity/ContentProducer.java new file mode 100644 index 0000000..456eae3 --- /dev/null +++ b/src/org/apache/http/entity/ContentProducer.java @@ -0,0 +1,53 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/ContentProducer.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An abstract entity content producer. + * + *<p>Content producers are expected to be able to produce their + * content multiple times</p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public interface ContentProducer { + + void writeTo(OutputStream outstream) throws IOException; + +} diff --git a/src/org/apache/http/entity/EntityTemplate.java b/src/org/apache/http/entity/EntityTemplate.java new file mode 100644 index 0000000..0c6002e --- /dev/null +++ b/src/org/apache/http/entity/EntityTemplate.java @@ -0,0 +1,86 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/EntityTemplate.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Entity that delegates the process of content generation to an abstract + * content producer. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public class EntityTemplate extends AbstractHttpEntity { + + private final ContentProducer contentproducer; + + public EntityTemplate(final ContentProducer contentproducer) { + super(); + if (contentproducer == null) { + throw new IllegalArgumentException("Content producer may not be null"); + } + this.contentproducer = contentproducer; + } + + public long getContentLength() { + return -1; + } + + public InputStream getContent() { + throw new UnsupportedOperationException("Entity template does not implement getContent()"); + } + + public boolean isRepeatable() { + return true; + } + + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + this.contentproducer.writeTo(outstream); + } + + public boolean isStreaming() { + return true; + } + + public void consumeContent() throws IOException { + } + +} diff --git a/src/org/apache/http/entity/FileEntity.java b/src/org/apache/http/entity/FileEntity.java new file mode 100644 index 0000000..a991058 --- /dev/null +++ b/src/org/apache/http/entity/FileEntity.java @@ -0,0 +1,106 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/FileEntity.java $ + * $Revision: 604625 $ + * $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An entity whose content is retrieved from a file. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 604625 $ + * + * @since 4.0 + */ +public class FileEntity extends AbstractHttpEntity implements Cloneable { + + protected final File file; + + public FileEntity(final File file, final String contentType) { + super(); + if (file == null) { + throw new IllegalArgumentException("File may not be null"); + } + this.file = file; + setContentType(contentType); + } + + public boolean isRepeatable() { + return true; + } + + public long getContentLength() { + return this.file.length(); + } + + public InputStream getContent() throws IOException { + return new FileInputStream(this.file); + } + + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + InputStream instream = new FileInputStream(this.file); + try { + byte[] tmp = new byte[4096]; + int l; + while ((l = instream.read(tmp)) != -1) { + outstream.write(tmp, 0, l); + } + outstream.flush(); + } finally { + instream.close(); + } + } + + /** + * Tells that this entity is not streaming. + * + * @return <code>false</code> + */ + public boolean isStreaming() { + return false; + } + + public Object clone() throws CloneNotSupportedException { + // File instance is considered immutable + // No need to make a copy of it + return super.clone(); + } + +} // class FileEntity diff --git a/src/org/apache/http/entity/HttpEntityWrapper.java b/src/org/apache/http/entity/HttpEntityWrapper.java new file mode 100644 index 0000000..17a4149 --- /dev/null +++ b/src/org/apache/http/entity/HttpEntityWrapper.java @@ -0,0 +1,113 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/HttpEntityWrapper.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; + +/** + * Base class for wrapping entities. + * Keeps a {@link #wrappedEntity wrappedEntity} and delegates all + * calls to it. Implementations of wrapping entities can derive + * from this class and need to override only those methods that + * should not be delegated to the wrapped entity. + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public class HttpEntityWrapper implements HttpEntity { + + /** The wrapped entity. */ + protected HttpEntity wrappedEntity; + + /** + * Creates a new entity wrapper. + * + * @param wrapped the entity to wrap + */ + public HttpEntityWrapper(HttpEntity wrapped) { + super(); + + if (wrapped == null) { + throw new IllegalArgumentException + ("wrapped entity must not be null"); + } + wrappedEntity = wrapped; + + } // constructor + + + public boolean isRepeatable() { + return wrappedEntity.isRepeatable(); + } + + public boolean isChunked() { + return wrappedEntity.isChunked(); + } + + public long getContentLength() { + return wrappedEntity.getContentLength(); + } + + public Header getContentType() { + return wrappedEntity.getContentType(); + } + + public Header getContentEncoding() { + return wrappedEntity.getContentEncoding(); + } + + public InputStream getContent() + throws IOException { + return wrappedEntity.getContent(); + } + + public void writeTo(OutputStream outstream) + throws IOException { + wrappedEntity.writeTo(outstream); + } + + public boolean isStreaming() { + return wrappedEntity.isStreaming(); + } + + public void consumeContent() + throws IOException { + wrappedEntity.consumeContent(); + } + +} // class HttpEntityWrapper diff --git a/src/org/apache/http/entity/InputStreamEntity.java b/src/org/apache/http/entity/InputStreamEntity.java new file mode 100644 index 0000000..6d33fe4 --- /dev/null +++ b/src/org/apache/http/entity/InputStreamEntity.java @@ -0,0 +1,116 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/InputStreamEntity.java $ + * $Revision: 617591 $ + * $Date: 2008-02-01 10:21:17 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A streamed entity obtaining content from an {@link InputStream InputStream}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 617591 $ + * + * @since 4.0 + */ +public class InputStreamEntity extends AbstractHttpEntity { + + private final static int BUFFER_SIZE = 2048; + + private final InputStream content; + private final long length; + private boolean consumed = false; + + public InputStreamEntity(final InputStream instream, long length) { + super(); + if (instream == null) { + throw new IllegalArgumentException("Source input stream may not be null"); + } + this.content = instream; + this.length = length; + } + + public boolean isRepeatable() { + return false; + } + + public long getContentLength() { + return this.length; + } + + public InputStream getContent() throws IOException { + return this.content; + } + + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + InputStream instream = this.content; + byte[] buffer = new byte[BUFFER_SIZE]; + int l; + if (this.length < 0) { + // consume until EOF + while ((l = instream.read(buffer)) != -1) { + outstream.write(buffer, 0, l); + } + } else { + // consume no more than length + long remaining = this.length; + while (remaining > 0) { + l = instream.read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining)); + if (l == -1) { + break; + } + outstream.write(buffer, 0, l); + remaining -= l; + } + } + this.consumed = true; + } + + // non-javadoc, see interface HttpEntity + public boolean isStreaming() { + return !this.consumed; + } + + // non-javadoc, see interface HttpEntity + public void consumeContent() throws IOException { + this.consumed = true; + // If the input stream is from a connection, closing it will read to + // the end of the content. Otherwise, we don't care what it does. + this.content.close(); + } + +} // class InputStreamEntity diff --git a/src/org/apache/http/entity/SerializableEntity.java b/src/org/apache/http/entity/SerializableEntity.java new file mode 100644 index 0000000..171977b --- /dev/null +++ b/src/org/apache/http/entity/SerializableEntity.java @@ -0,0 +1,107 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/SerializableEntity.java $ + * $Revision: 647816 $ + * $Date: 2008-04-14 07:37:13 -0700 (Mon, 14 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; + +public class SerializableEntity extends AbstractHttpEntity { + + private byte[] objSer; + + private Serializable objRef; + + public SerializableEntity(Serializable ser, boolean bufferize) throws IOException { + super(); + if (ser == null) { + throw new IllegalArgumentException("Source object may not be null"); + } + + if (bufferize) { + createBytes(ser); + } else { + this.objRef = ser; + } + } + + private void createBytes(Serializable ser) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(ser); + out.flush(); + this.objSer = baos.toByteArray(); + } + + public InputStream getContent() throws IOException, IllegalStateException { + if (this.objSer == null) { + createBytes(this.objRef); + } + return new ByteArrayInputStream(this.objSer); + } + + public long getContentLength() { + if (this.objSer == null) { + return -1; + } else { + return this.objSer.length; + } + } + + public boolean isRepeatable() { + return true; + } + + public boolean isStreaming() { + return this.objSer == null; + } + + public void writeTo(OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + + if (this.objSer == null) { + ObjectOutputStream out = new ObjectOutputStream(outstream); + out.writeObject(this.objRef); + out.flush(); + } else { + outstream.write(this.objSer); + outstream.flush(); + } + } + +} diff --git a/src/org/apache/http/entity/StringEntity.java b/src/org/apache/http/entity/StringEntity.java new file mode 100644 index 0000000..cbc382b --- /dev/null +++ b/src/org/apache/http/entity/StringEntity.java @@ -0,0 +1,106 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/StringEntity.java $ + * $Revision: 618367 $ + * $Date: 2008-02-04 10:26:06 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.entity; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.apache.http.protocol.HTTP; + +/** + * An entity whose content is retrieved from a string. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618367 $ + * + * @since 4.0 + */ +public class StringEntity extends AbstractHttpEntity implements Cloneable { + + protected final byte[] content; + + public StringEntity(final String s, String charset) + throws UnsupportedEncodingException { + super(); + if (s == null) { + throw new IllegalArgumentException("Source string may not be null"); + } + if (charset == null) { + charset = HTTP.DEFAULT_CONTENT_CHARSET; + } + this.content = s.getBytes(charset); + setContentType(HTTP.PLAIN_TEXT_TYPE + HTTP.CHARSET_PARAM + charset); + } + + public StringEntity(final String s) + throws UnsupportedEncodingException { + this(s, null); + } + + public boolean isRepeatable() { + return true; + } + + public long getContentLength() { + return this.content.length; + } + + public InputStream getContent() throws IOException { + return new ByteArrayInputStream(this.content); + } + + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + outstream.write(this.content); + outstream.flush(); + } + + /** + * Tells that this entity is not streaming. + * + * @return <code>false</code> + */ + public boolean isStreaming() { + return false; + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} // class StringEntity diff --git a/src/org/apache/http/entity/package.html b/src/org/apache/http/entity/package.html new file mode 100644 index 0000000..11d491e --- /dev/null +++ b/src/org/apache/http/entity/package.html @@ -0,0 +1,58 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/entity/package.html $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Representations for HTTP message entities. + +An {@link org.apache.http.HttpEntity entity} is the optional content of a +{@link org.apache.http.HttpMessage message}. +You'll find a basic selection of entity implementations here. +If you need to send an entity, you can provide it for example as a +{@link org.apache.http.entity.ByteArrayEntity byte array}, +{@link org.apache.http.entity.StringEntity string}, +{@link org.apache.http.entity.FileEntity file}, or through an arbitrary +{@link org.apache.http.entity.InputStreamEntity input stream}. +If you receive a message with an entity, you typically get that as a +{@link org.apache.http.entity.BasicHttpEntity basic} entity. +Entity implementations can be +{@link org.apache.http.entity.HttpEntityWrapper wrapped}, +for example to +{@link org.apache.http.entity.BufferedHttpEntity buffer} +the content in memory. + + + +</body> +</html> diff --git a/src/org/apache/http/impl/AbstractHttpClientConnection.java b/src/org/apache/http/impl/AbstractHttpClientConnection.java new file mode 100644 index 0000000..ebfaabb --- /dev/null +++ b/src/org/apache/http/impl/AbstractHttpClientConnection.java @@ -0,0 +1,212 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/AbstractHttpClientConnection.java $ + * $Revision: 627457 $ + * $Date: 2008-02-13 07:14:19 -0800 (Wed, 13 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; + +import org.apache.http.HttpClientConnection; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.impl.entity.EntityDeserializer; +import org.apache.http.impl.entity.EntitySerializer; +import org.apache.http.impl.entity.LaxContentLengthStrategy; +import org.apache.http.impl.entity.StrictContentLengthStrategy; +import org.apache.http.impl.io.HttpRequestWriter; +import org.apache.http.impl.io.HttpResponseParser; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.HttpMessageWriter; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpParams; + +/** + * Abstract client-side HTTP connection capable of transmitting and receiving data + * using arbitrary {@link SessionInputBuffer} and {@link SessionOutputBuffer} + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 627457 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpClientConnection implements HttpClientConnection { + + private final EntitySerializer entityserializer; + private final EntityDeserializer entitydeserializer; + + private SessionInputBuffer inbuffer = null; + private SessionOutputBuffer outbuffer = null; + private HttpMessageParser responseParser = null; + private HttpMessageWriter requestWriter = null; + private HttpConnectionMetricsImpl metrics = null; + + + + public AbstractHttpClientConnection() { + super(); + this.entityserializer = createEntitySerializer(); + this.entitydeserializer = createEntityDeserializer(); + } + + protected abstract void assertOpen() throws IllegalStateException; + + protected EntityDeserializer createEntityDeserializer() { + return new EntityDeserializer(new LaxContentLengthStrategy()); + } + + protected EntitySerializer createEntitySerializer() { + return new EntitySerializer(new StrictContentLengthStrategy()); + } + + protected HttpResponseFactory createHttpResponseFactory() { + return new DefaultHttpResponseFactory(); + } + + protected HttpMessageParser createResponseParser( + final SessionInputBuffer buffer, + final HttpResponseFactory responseFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new HttpResponseParser(buffer, null, responseFactory, params); + } + + protected HttpMessageWriter createRequestWriter( + final SessionOutputBuffer buffer, + final HttpParams params) { + // override in derived class to specify a line formatter + return new HttpRequestWriter(buffer, null, params); + } + + protected void init( + final SessionInputBuffer inbuffer, + final SessionOutputBuffer outbuffer, + final HttpParams params) { + if (inbuffer == null) { + throw new IllegalArgumentException("Input session buffer may not be null"); + } + if (outbuffer == null) { + throw new IllegalArgumentException("Output session buffer may not be null"); + } + this.inbuffer = inbuffer; + this.outbuffer = outbuffer; + this.responseParser = createResponseParser( + inbuffer, + createHttpResponseFactory(), + params); + this.requestWriter = createRequestWriter( + outbuffer, params); + this.metrics = new HttpConnectionMetricsImpl( + inbuffer.getMetrics(), + outbuffer.getMetrics()); + } + + public boolean isResponseAvailable(int timeout) throws IOException { + assertOpen(); + return this.inbuffer.isDataAvailable(timeout); + } + + public void sendRequestHeader(final HttpRequest request) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + assertOpen(); + this.requestWriter.write(request); + this.metrics.incrementRequestCount(); + } + + public void sendRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + assertOpen(); + if (request.getEntity() == null) { + return; + } + this.entityserializer.serialize( + this.outbuffer, + request, + request.getEntity()); + } + + protected void doFlush() throws IOException { + this.outbuffer.flush(); + } + + public void flush() throws IOException { + assertOpen(); + doFlush(); + } + + public HttpResponse receiveResponseHeader() + throws HttpException, IOException { + assertOpen(); + HttpResponse response = (HttpResponse) this.responseParser.parse(); + if (response.getStatusLine().getStatusCode() >= 200) { + this.metrics.incrementResponseCount(); + } + return response; + } + + public void receiveResponseEntity(final HttpResponse response) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + assertOpen(); + HttpEntity entity = this.entitydeserializer.deserialize(this.inbuffer, response); + response.setEntity(entity); + } + + public boolean isStale() { + if (!isOpen()) { + return true; + } + try { + this.inbuffer.isDataAvailable(1); + return false; + } catch (IOException ex) { + return true; + } + } + + public HttpConnectionMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/AbstractHttpServerConnection.java b/src/org/apache/http/impl/AbstractHttpServerConnection.java new file mode 100644 index 0000000..ef68ed3 --- /dev/null +++ b/src/org/apache/http/impl/AbstractHttpServerConnection.java @@ -0,0 +1,202 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/AbstractHttpServerConnection.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; + +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestFactory; +import org.apache.http.HttpResponse; +import org.apache.http.HttpServerConnection; +import org.apache.http.impl.entity.EntityDeserializer; +import org.apache.http.impl.entity.EntitySerializer; +import org.apache.http.impl.entity.LaxContentLengthStrategy; +import org.apache.http.impl.entity.StrictContentLengthStrategy; +import org.apache.http.impl.io.HttpRequestParser; +import org.apache.http.impl.io.HttpResponseWriter; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.HttpMessageWriter; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpParams; + +/** + * Abstract server-side HTTP connection capable of transmitting and receiving data + * using arbitrary {@link SessionInputBuffer} and {@link SessionOutputBuffer} + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpServerConnection implements HttpServerConnection { + + private final EntitySerializer entityserializer; + private final EntityDeserializer entitydeserializer; + + private SessionInputBuffer inbuffer = null; + private SessionOutputBuffer outbuffer = null; + private HttpMessageParser requestParser = null; + private HttpMessageWriter responseWriter = null; + private HttpConnectionMetricsImpl metrics = null; + + + + public AbstractHttpServerConnection() { + super(); + this.entityserializer = createEntitySerializer(); + this.entitydeserializer = createEntityDeserializer(); + } + + protected abstract void assertOpen() throws IllegalStateException; + + protected EntityDeserializer createEntityDeserializer() { + return new EntityDeserializer(new LaxContentLengthStrategy()); + } + + protected EntitySerializer createEntitySerializer() { + return new EntitySerializer(new StrictContentLengthStrategy()); + } + + protected HttpRequestFactory createHttpRequestFactory() { + return new DefaultHttpRequestFactory(); + } + + protected HttpMessageParser createRequestParser( + final SessionInputBuffer buffer, + final HttpRequestFactory requestFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new HttpRequestParser(buffer, null, requestFactory, params); + } + + protected HttpMessageWriter createResponseWriter( + final SessionOutputBuffer buffer, + final HttpParams params) { + // override in derived class to specify a line formatter + return new HttpResponseWriter(buffer, null, params); + } + + + protected void init( + final SessionInputBuffer inbuffer, + final SessionOutputBuffer outbuffer, + final HttpParams params) { + if (inbuffer == null) { + throw new IllegalArgumentException("Input session buffer may not be null"); + } + if (outbuffer == null) { + throw new IllegalArgumentException("Output session buffer may not be null"); + } + this.inbuffer = inbuffer; + this.outbuffer = outbuffer; + this.requestParser = createRequestParser( + inbuffer, + createHttpRequestFactory(), + params); + this.responseWriter = createResponseWriter( + outbuffer, params); + this.metrics = new HttpConnectionMetricsImpl( + inbuffer.getMetrics(), + outbuffer.getMetrics()); + } + + public HttpRequest receiveRequestHeader() + throws HttpException, IOException { + assertOpen(); + HttpRequest request = (HttpRequest) this.requestParser.parse(); + this.metrics.incrementRequestCount(); + return request; + } + + public void receiveRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + assertOpen(); + HttpEntity entity = this.entitydeserializer.deserialize(this.inbuffer, request); + request.setEntity(entity); + } + + protected void doFlush() throws IOException { + this.outbuffer.flush(); + } + + public void flush() throws IOException { + assertOpen(); + doFlush(); + } + + public void sendResponseHeader(final HttpResponse response) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + assertOpen(); + this.responseWriter.write(response); + if (response.getStatusLine().getStatusCode() >= 200) { + this.metrics.incrementResponseCount(); + } + } + + public void sendResponseEntity(final HttpResponse response) + throws HttpException, IOException { + if (response.getEntity() == null) { + return; + } + this.entityserializer.serialize( + this.outbuffer, + response, + response.getEntity()); + } + + public boolean isStale() { + assertOpen(); + try { + this.inbuffer.isDataAvailable(1); + return false; + } catch (IOException ex) { + return true; + } + } + + public HttpConnectionMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/DefaultConnectionReuseStrategy.java b/src/org/apache/http/impl/DefaultConnectionReuseStrategy.java new file mode 100644 index 0000000..da1d5fd --- /dev/null +++ b/src/org/apache/http/impl/DefaultConnectionReuseStrategy.java @@ -0,0 +1,182 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java $ + * $Revision: 602537 $ + * $Date: 2007-12-08 11:42:06 -0800 (Sat, 08 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpConnection; +import org.apache.http.HeaderIterator; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpVersion; +import org.apache.http.ParseException; +import org.apache.http.ProtocolVersion; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.TokenIterator; +import org.apache.http.message.BasicTokenIterator; + +/** + * Default implementation of a strategy deciding about connection re-use. + * The default implementation first checks some basics, for example + * whether the connection is still open or whether the end of the + * request entity can be determined without closing the connection. + * If these checks pass, the tokens in the "Connection" header will + * be examined. In the absence of a "Connection" header, the + * non-standard but commonly used "Proxy-Connection" header takes + * it's role. A token "close" indicates that the connection cannot + * be reused. If there is no such token, a token "keep-alive" indicates + * that the connection should be re-used. If neither token is found, + * or if there are no "Connection" headers, the default policy for + * the HTTP version is applied. Since HTTP/1.1, connections are re-used + * by default. Up until HTTP/1.0, connections are not re-used by default. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 602537 $ + * + * @since 4.0 + */ +public class DefaultConnectionReuseStrategy + implements ConnectionReuseStrategy { + + public DefaultConnectionReuseStrategy() { + super(); + } + + // see interface ConnectionReuseStrategy + public boolean keepAlive(final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException + ("HTTP response may not be null."); + } + if (context == null) { + throw new IllegalArgumentException + ("HTTP context may not be null."); + } + + HttpConnection conn = (HttpConnection) + context.getAttribute(ExecutionContext.HTTP_CONNECTION); + + if (conn != null && !conn.isOpen()) + return false; + // do NOT check for stale connection, that is an expensive operation + + // Check for a self-terminating entity. If the end of the entity will + // be indicated by closing the connection, there is no keep-alive. + HttpEntity entity = response.getEntity(); + ProtocolVersion ver = response.getStatusLine().getProtocolVersion(); + if (entity != null) { + if (entity.getContentLength() < 0) { + if (!entity.isChunked() || + ver.lessEquals(HttpVersion.HTTP_1_0)) { + // if the content length is not known and is not chunk + // encoded, the connection cannot be reused + return false; + } + } + } + + // Check for the "Connection" header. If that is absent, check for + // the "Proxy-Connection" header. The latter is an unspecified and + // broken but unfortunately common extension of HTTP. + HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE); + if (!hit.hasNext()) + hit = response.headerIterator("Proxy-Connection"); + + // Experimental usage of the "Connection" header in HTTP/1.0 is + // documented in RFC 2068, section 19.7.1. A token "keep-alive" is + // used to indicate that the connection should be persistent. + // Note that the final specification of HTTP/1.1 in RFC 2616 does not + // include this information. Neither is the "Connection" header + // mentioned in RFC 1945, which informally describes HTTP/1.0. + // + // RFC 2616 specifies "close" as the only connection token with a + // specific meaning: it disables persistent connections. + // + // The "Proxy-Connection" header is not formally specified anywhere, + // but is commonly used to carry one token, "close" or "keep-alive". + // The "Connection" header, on the other hand, is defined as a + // sequence of tokens, where each token is a header name, and the + // token "close" has the above-mentioned additional meaning. + // + // To get through this mess, we treat the "Proxy-Connection" header + // in exactly the same way as the "Connection" header, but only if + // the latter is missing. We scan the sequence of tokens for both + // "close" and "keep-alive". As "close" is specified by RFC 2068, + // it takes precedence and indicates a non-persistent connection. + // If there is no "close" but a "keep-alive", we take the hint. + + if (hit.hasNext()) { + try { + TokenIterator ti = createTokenIterator(hit); + boolean keepalive = false; + while (ti.hasNext()) { + final String token = ti.nextToken(); + if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) { + return false; + } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) { + // continue the loop, there may be a "close" afterwards + keepalive = true; + } + } + if (keepalive) + return true; + // neither "close" nor "keep-alive", use default policy + + } catch (ParseException px) { + // invalid connection header means no persistent connection + // we don't have logging in HttpCore, so the exception is lost + return false; + } + } + + // default since HTTP/1.1 is persistent, before it was non-persistent + return !ver.lessEquals(HttpVersion.HTTP_1_0); + } + + + /** + * Creates a token iterator from a header iterator. + * This method can be overridden to replace the implementation of + * the token iterator. + * + * @param hit the header iterator + * + * @return the token iterator + */ + protected TokenIterator createTokenIterator(HeaderIterator hit) { + return new BasicTokenIterator(hit); + } +} diff --git a/src/org/apache/http/impl/DefaultHttpClientConnection.java b/src/org/apache/http/impl/DefaultHttpClientConnection.java new file mode 100644 index 0000000..c0a96f5 --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpClientConnection.java @@ -0,0 +1,87 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpClientConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Default implementation of a client-side HTTP connection. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class DefaultHttpClientConnection extends SocketHttpClientConnection { + + public DefaultHttpClientConnection() { + super(); + } + + public void bind( + final Socket socket, + final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + assertNotOpen(); + socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + super.bind(socket, params); + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("["); + if (isOpen()) { + buffer.append(getRemotePort()); + } else { + buffer.append("closed"); + } + buffer.append("]"); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/impl/DefaultHttpRequestFactory.java b/src/org/apache/http/impl/DefaultHttpRequestFactory.java new file mode 100644 index 0000000..dee36c9 --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpRequestFactory.java @@ -0,0 +1,113 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpRequestFactory.java $ + * $Revision: 618367 $ + * $Date: 2008-02-04 10:26:06 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestFactory; +import org.apache.http.MethodNotSupportedException; +import org.apache.http.RequestLine; +import org.apache.http.message.BasicHttpEntityEnclosingRequest; +import org.apache.http.message.BasicHttpRequest; + +/** + * Default implementation of a factory for creating request objects. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618367 $ + * + * @since 4.0 + */ +public class DefaultHttpRequestFactory implements HttpRequestFactory { + + private static final String[] RFC2616_COMMON_METHODS = { + "GET" + }; + + private static final String[] RFC2616_ENTITY_ENC_METHODS = { + "POST", + "PUT" + }; + + private static final String[] RFC2616_SPECIAL_METHODS = { + "HEAD", + "OPTIONS", + "DELETE", + "TRACE" + }; + + + public DefaultHttpRequestFactory() { + super(); + } + + private static boolean isOneOf(final String[] methods, final String method) { + for (int i = 0; i < methods.length; i++) { + if (methods[i].equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + + public HttpRequest newHttpRequest(final RequestLine requestline) + throws MethodNotSupportedException { + if (requestline == null) { + throw new IllegalArgumentException("Request line may not be null"); + } + String method = requestline.getMethod(); + if (isOneOf(RFC2616_COMMON_METHODS, method)) { + return new BasicHttpRequest(requestline); + } else if (isOneOf(RFC2616_ENTITY_ENC_METHODS, method)) { + return new BasicHttpEntityEnclosingRequest(requestline); + } else if (isOneOf(RFC2616_SPECIAL_METHODS, method)) { + return new BasicHttpRequest(requestline); + } else { + throw new MethodNotSupportedException(method + " method not supported"); + } + } + + public HttpRequest newHttpRequest(final String method, final String uri) + throws MethodNotSupportedException { + if (isOneOf(RFC2616_COMMON_METHODS, method)) { + return new BasicHttpRequest(method, uri); + } else if (isOneOf(RFC2616_ENTITY_ENC_METHODS, method)) { + return new BasicHttpEntityEnclosingRequest(method, uri); + } else if (isOneOf(RFC2616_SPECIAL_METHODS, method)) { + return new BasicHttpRequest(method, uri); + } else { + throw new MethodNotSupportedException(method + + " method not supported"); + } + } + +} diff --git a/src/org/apache/http/impl/DefaultHttpResponseFactory.java b/src/org/apache/http/impl/DefaultHttpResponseFactory.java new file mode 100644 index 0000000..40a2c9a --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpResponseFactory.java @@ -0,0 +1,121 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpResponseFactory.java $ + * $Revision: 618367 $ + * $Date: 2008-02-04 10:26:06 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.util.Locale; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.message.BasicHttpResponse; +import org.apache.http.message.BasicStatusLine; +import org.apache.http.protocol.HttpContext; +import org.apache.http.ReasonPhraseCatalog; +import org.apache.http.impl.EnglishReasonPhraseCatalog; + +/** + * Default implementation of a factory for creating response objects. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618367 $ + * + * @since 4.0 + */ +public class DefaultHttpResponseFactory implements HttpResponseFactory { + + /** The catalog for looking up reason phrases. */ + protected final ReasonPhraseCatalog reasonCatalog; + + + /** + * Creates a new response factory with the given catalog. + * + * @param catalog the catalog of reason phrases + */ + public DefaultHttpResponseFactory(ReasonPhraseCatalog catalog) { + if (catalog == null) { + throw new IllegalArgumentException + ("Reason phrase catalog must not be null."); + } + this.reasonCatalog = catalog; + } + + /** + * Creates a new response factory with the default catalog. + * The default catalog is + * {@link EnglishReasonPhraseCatalog EnglishReasonPhraseCatalog}. + */ + public DefaultHttpResponseFactory() { + this(EnglishReasonPhraseCatalog.INSTANCE); + } + + + // non-javadoc, see interface HttpResponseFactory + public HttpResponse newHttpResponse(final ProtocolVersion ver, + final int status, + HttpContext context) { + if (ver == null) { + throw new IllegalArgumentException("HTTP version may not be null"); + } + final Locale loc = determineLocale(context); + final String reason = reasonCatalog.getReason(status, loc); + StatusLine statusline = new BasicStatusLine(ver, status, reason); + return new BasicHttpResponse(statusline, reasonCatalog, loc); + } + + + // non-javadoc, see interface HttpResponseFactory + public HttpResponse newHttpResponse(final StatusLine statusline, + HttpContext context) { + if (statusline == null) { + throw new IllegalArgumentException("Status line may not be null"); + } + final Locale loc = determineLocale(context); + return new BasicHttpResponse(statusline, reasonCatalog, loc); + } + + + /** + * Determines the locale of the response. + * The implementation in this class always returns the default locale. + * + * @param context the context from which to determine the locale, or + * <code>null</code> to use the default locale + * + * @return the locale for the response, never <code>null</code> + */ + protected Locale determineLocale(HttpContext context) { + return Locale.getDefault(); + } +} diff --git a/src/org/apache/http/impl/DefaultHttpServerConnection.java b/src/org/apache/http/impl/DefaultHttpServerConnection.java new file mode 100644 index 0000000..d296fc8 --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpServerConnection.java @@ -0,0 +1,85 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpServerConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Default implementation of a server-side HTTP connection. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class DefaultHttpServerConnection extends SocketHttpServerConnection { + + public DefaultHttpServerConnection() { + super(); + } + + public void bind(final Socket socket, final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + assertNotOpen(); + socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + super.bind(socket, params); + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("["); + if (isOpen()) { + buffer.append(getRemotePort()); + } else { + buffer.append("closed"); + } + buffer.append("]"); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/impl/EnglishReasonPhraseCatalog.java b/src/org/apache/http/impl/EnglishReasonPhraseCatalog.java new file mode 100644 index 0000000..f1aeee1 --- /dev/null +++ b/src/org/apache/http/impl/EnglishReasonPhraseCatalog.java @@ -0,0 +1,234 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/EnglishReasonPhraseCatalog.java $ + * $Revision: 505744 $ + * $Date: 2007-02-10 10:58:45 -0800 (Sat, 10 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.util.Locale; + +import org.apache.http.HttpStatus; +import org.apache.http.ReasonPhraseCatalog; + + +/** + * English reason phrases for HTTP status codes. + * All status codes defined in RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), and + * RFC2518 (WebDAV) are supported. + * + * @author Unascribed + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * + * @version $Revision: 505744 $ + */ +public class EnglishReasonPhraseCatalog + implements ReasonPhraseCatalog { + + // static array with english reason phrases defined below + + /** + * The default instance of this catalog. + * This catalog is thread safe, so there typically + * is no need to create other instances. + */ + public final static EnglishReasonPhraseCatalog INSTANCE = + new EnglishReasonPhraseCatalog(); + + + /** + * Restricted default constructor, for derived classes. + * If you need an instance of this class, use {@link #INSTANCE INSTANCE}. + */ + protected EnglishReasonPhraseCatalog() { + // no body + } + + + /** + * Obtains the reason phrase for a status code. + * + * @param status the status code, in the range 100-599 + * @param loc ignored + * + * @return the reason phrase, or <code>null</code> + */ + public String getReason(int status, Locale loc) { + if ((status < 100) || (status >= 600)) { + throw new IllegalArgumentException + ("Unknown category for status code " + status + "."); + } + + final int category = status / 100; + final int subcode = status - 100*category; + + String reason = null; + if (REASON_PHRASES[category].length > subcode) + reason = REASON_PHRASES[category][subcode]; + + return reason; + } + + + /** Reason phrases lookup table. */ + private static final String[][] REASON_PHRASES = new String[][]{ + null, + new String[3], // 1xx + new String[8], // 2xx + new String[8], // 3xx + new String[25], // 4xx + new String[8] // 5xx + }; + + + + /** + * Stores the given reason phrase, by status code. + * Helper method to initialize the static lookup table. + * + * @param status the status code for which to define the phrase + * @param reason the reason phrase for this status code + */ + private static void setReason(int status, String reason) { + final int category = status / 100; + final int subcode = status - 100*category; + REASON_PHRASES[category][subcode] = reason; + } + + + // ----------------------------------------------------- Static Initializer + + /** Set up status code to "reason phrase" map. */ + static { + // HTTP 1.0 Server status codes -- see RFC 1945 + setReason(HttpStatus.SC_OK, + "OK"); + setReason(HttpStatus.SC_CREATED, + "Created"); + setReason(HttpStatus.SC_ACCEPTED, + "Accepted"); + setReason(HttpStatus.SC_NO_CONTENT, + "No Content"); + setReason(HttpStatus.SC_MOVED_PERMANENTLY, + "Moved Permanently"); + setReason(HttpStatus.SC_MOVED_TEMPORARILY, + "Moved Temporarily"); + setReason(HttpStatus.SC_NOT_MODIFIED, + "Not Modified"); + setReason(HttpStatus.SC_BAD_REQUEST, + "Bad Request"); + setReason(HttpStatus.SC_UNAUTHORIZED, + "Unauthorized"); + setReason(HttpStatus.SC_FORBIDDEN, + "Forbidden"); + setReason(HttpStatus.SC_NOT_FOUND, + "Not Found"); + setReason(HttpStatus.SC_INTERNAL_SERVER_ERROR, + "Internal Server Error"); + setReason(HttpStatus.SC_NOT_IMPLEMENTED, + "Not Implemented"); + setReason(HttpStatus.SC_BAD_GATEWAY, + "Bad Gateway"); + setReason(HttpStatus.SC_SERVICE_UNAVAILABLE, + "Service Unavailable"); + + // HTTP 1.1 Server status codes -- see RFC 2048 + setReason(HttpStatus.SC_CONTINUE, + "Continue"); + setReason(HttpStatus.SC_TEMPORARY_REDIRECT, + "Temporary Redirect"); + setReason(HttpStatus.SC_METHOD_NOT_ALLOWED, + "Method Not Allowed"); + setReason(HttpStatus.SC_CONFLICT, + "Conflict"); + setReason(HttpStatus.SC_PRECONDITION_FAILED, + "Precondition Failed"); + setReason(HttpStatus.SC_REQUEST_TOO_LONG, + "Request Too Long"); + setReason(HttpStatus.SC_REQUEST_URI_TOO_LONG, + "Request-URI Too Long"); + setReason(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, + "Unsupported Media Type"); + setReason(HttpStatus.SC_MULTIPLE_CHOICES, + "Multiple Choices"); + setReason(HttpStatus.SC_SEE_OTHER, + "See Other"); + setReason(HttpStatus.SC_USE_PROXY, + "Use Proxy"); + setReason(HttpStatus.SC_PAYMENT_REQUIRED, + "Payment Required"); + setReason(HttpStatus.SC_NOT_ACCEPTABLE, + "Not Acceptable"); + setReason(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, + "Proxy Authentication Required"); + setReason(HttpStatus.SC_REQUEST_TIMEOUT, + "Request Timeout"); + + setReason(HttpStatus.SC_SWITCHING_PROTOCOLS, + "Switching Protocols"); + setReason(HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, + "Non Authoritative Information"); + setReason(HttpStatus.SC_RESET_CONTENT, + "Reset Content"); + setReason(HttpStatus.SC_PARTIAL_CONTENT, + "Partial Content"); + setReason(HttpStatus.SC_GATEWAY_TIMEOUT, + "Gateway Timeout"); + setReason(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED, + "Http Version Not Supported"); + setReason(HttpStatus.SC_GONE, + "Gone"); + setReason(HttpStatus.SC_LENGTH_REQUIRED, + "Length Required"); + setReason(HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE, + "Requested Range Not Satisfiable"); + setReason(HttpStatus.SC_EXPECTATION_FAILED, + "Expectation Failed"); + + // WebDAV Server-specific status codes + setReason(HttpStatus.SC_PROCESSING, + "Processing"); + setReason(HttpStatus.SC_MULTI_STATUS, + "Multi-Status"); + setReason(HttpStatus.SC_UNPROCESSABLE_ENTITY, + "Unprocessable Entity"); + setReason(HttpStatus.SC_INSUFFICIENT_SPACE_ON_RESOURCE, + "Insufficient Space On Resource"); + setReason(HttpStatus.SC_METHOD_FAILURE, + "Method Failure"); + setReason(HttpStatus.SC_LOCKED, + "Locked"); + setReason(HttpStatus.SC_INSUFFICIENT_STORAGE, + "Insufficient Storage"); + setReason(HttpStatus.SC_FAILED_DEPENDENCY, + "Failed Dependency"); + } + + +} diff --git a/src/org/apache/http/impl/HttpConnectionMetricsImpl.java b/src/org/apache/http/impl/HttpConnectionMetricsImpl.java new file mode 100644 index 0000000..4f4eacf --- /dev/null +++ b/src/org/apache/http/impl/HttpConnectionMetricsImpl.java @@ -0,0 +1,146 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/HttpConnectionMetricsImpl.java $ + * $Revision: 548031 $ + * $Date: 2007-06-17 04:28:38 -0700 (Sun, 17 Jun 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.util.HashMap; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.io.HttpTransportMetrics; + +/** + * Implementation of the metrics interface. + */ +public class HttpConnectionMetricsImpl implements HttpConnectionMetrics { + + public static final String REQUEST_COUNT = "http.request-count"; + public static final String RESPONSE_COUNT = "http.response-count"; + public static final String SENT_BYTES_COUNT = "http.sent-bytes-count"; + public static final String RECEIVED_BYTES_COUNT = "http.received-bytes-count"; + + private final HttpTransportMetrics inTransportMetric; + private final HttpTransportMetrics outTransportMetric; + private long requestCount = 0; + private long responseCount = 0; + + /** + * The cache map for all metrics values. + */ + private HashMap metricsCache; + + public HttpConnectionMetricsImpl( + final HttpTransportMetrics inTransportMetric, + final HttpTransportMetrics outTransportMetric) { + super(); + this.inTransportMetric = inTransportMetric; + this.outTransportMetric = outTransportMetric; + } + + /* ------------------ Public interface method -------------------------- */ + + public long getReceivedBytesCount() { + if (this.inTransportMetric != null) { + return this.inTransportMetric.getBytesTransferred(); + } else { + return -1; + } + } + + public long getSentBytesCount() { + if (this.outTransportMetric != null) { + return this.outTransportMetric.getBytesTransferred(); + } else { + return -1; + } + } + + public long getRequestCount() { + return this.requestCount; + } + + public void incrementRequestCount() { + this.requestCount++; + } + + public long getResponseCount() { + return this.responseCount; + } + + public void incrementResponseCount() { + this.responseCount++; + } + + public Object getMetric(final String metricName) { + Object value = null; + if (this.metricsCache != null) { + value = this.metricsCache.get(metricName); + } + if (value == null) { + if (REQUEST_COUNT.equals(metricName)) { + value = new Long(requestCount); + } else if (RESPONSE_COUNT.equals(metricName)) { + value = new Long(responseCount); + } else if (RECEIVED_BYTES_COUNT.equals(metricName)) { + if (this.inTransportMetric != null) { + return new Long(this.inTransportMetric.getBytesTransferred()); + } else { + return null; + } + } else if (SENT_BYTES_COUNT.equals(metricName)) { + if (this.outTransportMetric != null) { + return new Long(this.outTransportMetric.getBytesTransferred()); + } else { + return null; + } + } + } + return value; + } + + public void setMetric(final String metricName, Object obj) { + if (this.metricsCache == null) { + this.metricsCache = new HashMap(); + } + this.metricsCache.put(metricName, obj); + } + + public void reset() { + if (this.outTransportMetric != null) { + this.outTransportMetric.reset(); + } + if (this.inTransportMetric != null) { + this.inTransportMetric.reset(); + } + this.requestCount = 0; + this.responseCount = 0; + this.metricsCache = null; + } + +} diff --git a/src/org/apache/http/impl/NoConnectionReuseStrategy.java b/src/org/apache/http/impl/NoConnectionReuseStrategy.java new file mode 100644 index 0000000..c7a5f73 --- /dev/null +++ b/src/org/apache/http/impl/NoConnectionReuseStrategy.java @@ -0,0 +1,65 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/NoConnectionReuseStrategy.java $ + * $Revision: 502684 $ + * $Date: 2007-02-02 10:25:38 -0800 (Fri, 02 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpResponse; +import org.apache.http.protocol.HttpContext; + + +/** + * A strategy that never re-uses a connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 502684 $ + * + * @since 4.0 + */ +public class NoConnectionReuseStrategy implements ConnectionReuseStrategy { + + // default constructor + + + // non-JavaDoc, see interface ConnectionReuseStrategy + public boolean keepAlive(final HttpResponse response, final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + return false; + } + +} diff --git a/src/org/apache/http/impl/SocketHttpClientConnection.java b/src/org/apache/http/impl/SocketHttpClientConnection.java new file mode 100644 index 0000000..1e551e0 --- /dev/null +++ b/src/org/apache/http/impl/SocketHttpClientConnection.java @@ -0,0 +1,208 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/SocketHttpClientConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; + +import org.apache.http.HttpInetConnection; +import org.apache.http.impl.io.SocketInputBuffer; +import org.apache.http.impl.io.SocketOutputBuffer; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Implementation of a client-side HTTP connection that can be bound to a + * network Socket in order to receive and transmit data. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class SocketHttpClientConnection + extends AbstractHttpClientConnection implements HttpInetConnection { + + private volatile boolean open; + private Socket socket = null; + + public SocketHttpClientConnection() { + super(); + } + + protected void assertNotOpen() { + if (this.open) { + throw new IllegalStateException("Connection is already open"); + } + } + + protected void assertOpen() { + if (!this.open) { + throw new IllegalStateException("Connection is not open"); + } + } + + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketInputBuffer(socket, buffersize, params); + } + + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketOutputBuffer(socket, buffersize, params); + } + + protected void bind( + final Socket socket, + final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.socket = socket; + + int buffersize = HttpConnectionParams.getSocketBufferSize(params); + + init( + createSessionInputBuffer(socket, buffersize, params), + createSessionOutputBuffer(socket, buffersize, params), + params); + + this.open = true; + } + + public boolean isOpen() { + return this.open; + } + + protected Socket getSocket() { + return this.socket; + } + + public InetAddress getLocalAddress() { + if (this.socket != null) { + return this.socket.getLocalAddress(); + } else { + return null; + } + } + + public int getLocalPort() { + if (this.socket != null) { + return this.socket.getLocalPort(); + } else { + return -1; + } + } + + public InetAddress getRemoteAddress() { + if (this.socket != null) { + return this.socket.getInetAddress(); + } else { + return null; + } + } + + public int getRemotePort() { + if (this.socket != null) { + return this.socket.getPort(); + } else { + return -1; + } + } + + public void setSocketTimeout(int timeout) { + assertOpen(); + if (this.socket != null) { + try { + this.socket.setSoTimeout(timeout); + } catch (SocketException ignore) { + // It is not quite clear from the Sun's documentation if there are any + // other legitimate cases for a socket exception to be thrown when setting + // SO_TIMEOUT besides the socket being already closed + } + } + } + + public int getSocketTimeout() { + if (this.socket != null) { + try { + return this.socket.getSoTimeout(); + } catch (SocketException ignore) { + return -1; + } + } else { + return -1; + } + } + + public void shutdown() throws IOException { + this.open = false; + Socket tmpsocket = this.socket; + if (tmpsocket != null) { + tmpsocket.close(); + } + } + + public void close() throws IOException { + if (!this.open) { + return; + } + this.open = false; + doFlush(); + try { + try { + this.socket.shutdownOutput(); + } catch (IOException ignore) { + } + try { + this.socket.shutdownInput(); + } catch (IOException ignore) { + } + } catch (UnsupportedOperationException ignore) { + // if one isn't supported, the other one isn't either + } + this.socket.close(); + } + +} diff --git a/src/org/apache/http/impl/SocketHttpServerConnection.java b/src/org/apache/http/impl/SocketHttpServerConnection.java new file mode 100644 index 0000000..cfa2bf9 --- /dev/null +++ b/src/org/apache/http/impl/SocketHttpServerConnection.java @@ -0,0 +1,202 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/SocketHttpServerConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; + +import org.apache.http.HttpInetConnection; +import org.apache.http.impl.io.SocketInputBuffer; +import org.apache.http.impl.io.SocketOutputBuffer; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Implementation of a server-side HTTP connection that can be bound to a + * network Socket in order to receive and transmit data. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class SocketHttpServerConnection extends + AbstractHttpServerConnection implements HttpInetConnection { + + private volatile boolean open; + private Socket socket = null; + + public SocketHttpServerConnection() { + super(); + } + + protected void assertNotOpen() { + if (this.open) { + throw new IllegalStateException("Connection is already open"); + } + } + + protected void assertOpen() { + if (!this.open) { + throw new IllegalStateException("Connection is not open"); + } + } + + protected SessionInputBuffer createHttpDataReceiver( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketInputBuffer(socket, buffersize, params); + } + + protected SessionOutputBuffer createHttpDataTransmitter( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketOutputBuffer(socket, buffersize, params); + } + + protected void bind(final Socket socket, final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.socket = socket; + + int buffersize = HttpConnectionParams.getSocketBufferSize(params); + + init( + createHttpDataReceiver(socket, buffersize, params), + createHttpDataTransmitter(socket, buffersize, params), + params); + + this.open = true; + } + + protected Socket getSocket() { + return this.socket; + } + + public boolean isOpen() { + return this.open; + } + + public InetAddress getLocalAddress() { + if (this.socket != null) { + return this.socket.getLocalAddress(); + } else { + return null; + } + } + + public int getLocalPort() { + if (this.socket != null) { + return this.socket.getLocalPort(); + } else { + return -1; + } + } + + public InetAddress getRemoteAddress() { + if (this.socket != null) { + return this.socket.getInetAddress(); + } else { + return null; + } + } + + public int getRemotePort() { + if (this.socket != null) { + return this.socket.getPort(); + } else { + return -1; + } + } + + public void setSocketTimeout(int timeout) { + assertOpen(); + if (this.socket != null) { + try { + this.socket.setSoTimeout(timeout); + } catch (SocketException ignore) { + // It is not quite clear from the Sun's documentation if there are any + // other legitimate cases for a socket exception to be thrown when setting + // SO_TIMEOUT besides the socket being already closed + } + } + } + + public int getSocketTimeout() { + if (this.socket != null) { + try { + return this.socket.getSoTimeout(); + } catch (SocketException ignore) { + return -1; + } + } else { + return -1; + } + } + + public void shutdown() throws IOException { + this.open = false; + Socket tmpsocket = this.socket; + if (tmpsocket != null) { + tmpsocket.close(); + } + } + + public void close() throws IOException { + if (!this.open) { + return; + } + this.open = false; + doFlush(); + try { + this.socket.shutdownOutput(); + } catch (IOException ignore) { + } + try { + this.socket.shutdownInput(); + } catch (IOException ignore) { + } + this.socket.close(); + } + +} diff --git a/src/org/apache/http/impl/auth/AuthSchemeBase.java b/src/org/apache/http/impl/auth/AuthSchemeBase.java new file mode 100644 index 0000000..689ce5d --- /dev/null +++ b/src/org/apache/http/impl/auth/AuthSchemeBase.java @@ -0,0 +1,128 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java $ + * $Revision: 653867 $ + * $Date: 2008-05-06 11:17:29 -0700 (Tue, 06 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract authentication scheme class that serves as a basis + * for all authentication schemes supported by HttpClient. This class + * defines the generic way of parsing an authentication challenge. It + * does not make any assumptions regarding the format of the challenge + * nor does it impose any specific way of responding to that challenge. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> +*/ +public abstract class AuthSchemeBase implements AuthScheme { + + /** + * Flag whether authenticating against a proxy. + */ + private boolean proxy; + + public AuthSchemeBase() { + super(); + } + + /** + * Processes the given challenge token. Some authentication schemes + * may involve multiple challenge-response exchanges. Such schemes must be able + * to maintain the state information when dealing with sequential challenges + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public void processChallenge(final Header header) throws MalformedChallengeException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + String authheader = header.getName(); + if (authheader.equalsIgnoreCase(AUTH.WWW_AUTH)) { + this.proxy = false; + } else if (authheader.equalsIgnoreCase(AUTH.PROXY_AUTH)) { + this.proxy = true; + } else { + throw new MalformedChallengeException("Unexpected header name: " + authheader); + } + + CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int endIndex = pos; + String s = buffer.substring(beginIndex, endIndex); + if (!s.equalsIgnoreCase(getSchemeName())) { + throw new MalformedChallengeException("Invalid scheme identifier: " + s); + } + + parseChallenge(buffer, pos, buffer.length()); + } + + protected abstract void parseChallenge( + CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException; + + /** + * Returns <code>true</code> if authenticating against a proxy, <code>false</code> + * otherwise. + * + * @return <code>true</code> if authenticating against a proxy, <code>false</code> + * otherwise + */ + public boolean isProxy() { + return this.proxy; + } + +} diff --git a/src/org/apache/http/impl/auth/BasicScheme.java b/src/org/apache/http/impl/auth/BasicScheme.java new file mode 100644 index 0000000..88ea110 --- /dev/null +++ b/src/org/apache/http/impl/auth/BasicScheme.java @@ -0,0 +1,185 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/BasicScheme.java $ + * $Revision: 658430 $ + * $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.commons.codec.binary.Base64; +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.auth.params.AuthParams; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.EncodingUtils; + +/** + * <p> + * Basic authentication scheme as defined in RFC 2617. + * </p> + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Ortwin Glueck + * @author Sean C. Sullivan + * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ + +public class BasicScheme extends RFC2617Scheme { + + /** Whether the basic authentication process is complete */ + private boolean complete; + + /** + * Default constructor for the basic authetication scheme. + */ + public BasicScheme() { + super(); + this.complete = false; + } + + /** + * Returns textual designation of the basic authentication scheme. + * + * @return <code>basic</code> + */ + public String getSchemeName() { + return "basic"; + } + + /** + * Processes the Basic challenge. + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + @Override + public void processChallenge( + final Header header) throws MalformedChallengeException { + super.processChallenge(header); + this.complete = true; + } + + /** + * Tests if the Basic authentication process has been completed. + * + * @return <tt>true</tt> if Basic authorization has been processed, + * <tt>false</tt> otherwise. + */ + public boolean isComplete() { + return this.complete; + } + + /** + * Returns <tt>false</tt>. Basic authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; + } + + /** + * Produces basic authorization header for the given set of {@link Credentials}. + * + * @param credentials The set of credentials to be used for athentication + * @param request The request being authenticated + * @throws org.apache.http.auth.InvalidCredentialsException if authentication credentials + * are not valid or not applicable for this authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a basic authorization string + */ + public Header authenticate( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + + String charset = AuthParams.getCredentialCharset(request.getParams()); + return authenticate(credentials, charset, isProxy()); + } + + /** + * Returns a basic <tt>Authorization</tt> header value for the given + * {@link Credentials} and charset. + * + * @param credentials The credentials to encode. + * @param charset The charset to use for encoding the credentials + * + * @return a basic authorization header + */ + public static Header authenticate( + final Credentials credentials, + final String charset, + boolean proxy) { + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (charset == null) { + throw new IllegalArgumentException("charset may not be null"); + } + + StringBuilder tmp = new StringBuilder(); + tmp.append(credentials.getUserPrincipal().getName()); + tmp.append(":"); + tmp.append((credentials.getPassword() == null) ? "null" : credentials.getPassword()); + + byte[] base64password = Base64.encodeBase64( + EncodingUtils.getBytes(tmp.toString(), charset)); + + CharArrayBuffer buffer = new CharArrayBuffer(32); + if (proxy) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": Basic "); + buffer.append(base64password, 0, base64password.length); + + return new BufferedHeader(buffer); + } + +} diff --git a/src/org/apache/http/impl/auth/BasicSchemeFactory.java b/src/org/apache/http/impl/auth/BasicSchemeFactory.java new file mode 100644 index 0000000..c5d28b0 --- /dev/null +++ b/src/org/apache/http/impl/auth/BasicSchemeFactory.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/BasicSchemeFactory.java $ + * $Revision: 534839 $ + * $Date: 2007-05-03 06:03:41 -0700 (Thu, 03 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthSchemeFactory; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BasicSchemeFactory implements AuthSchemeFactory { + + public AuthScheme newInstance(final HttpParams params) { + return new BasicScheme(); + } + +} diff --git a/src/org/apache/http/impl/auth/DigestScheme.java b/src/org/apache/http/impl/auth/DigestScheme.java new file mode 100644 index 0000000..803807b --- /dev/null +++ b/src/org/apache/http/impl/auth/DigestScheme.java @@ -0,0 +1,484 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/DigestScheme.java $ + * $Revision: 659595 $ + * $Date: 2008-05-23 09:47:14 -0700 (Fri, 23 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.auth.params.AuthParams; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.message.BasicHeaderValueFormatter; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.EncodingUtils; + +/** + * <p> + * Digest authentication scheme as defined in RFC 2617. + * Both MD5 (default) and MD5-sess are supported. + * Currently only qop=auth or no qop is supported. qop=auth-int + * is unsupported. If auth and auth-int are provided, auth is + * used. + * </p> + * <p> + * Credential charset is configured via the + * {@link org.apache.http.auth.params.AuthPNames#CREDENTIAL_CHARSET + * credential charset} parameter. + * Since the digest username is included as clear text in the generated + * Authentication header, the charset of the username must be compatible + * with the + * {@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET + * http element charset}. + * </p> + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Ortwin Glueck + * @author Sean C. Sullivan + * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ + +public class DigestScheme extends RFC2617Scheme { + + /** + * Hexa values used when creating 32 character long digest in HTTP DigestScheme + * in case of authentication. + * + * @see #encode(byte[]) + */ + private static final char[] HEXADECIMAL = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' + }; + + /** Whether the digest authentication process is complete */ + private boolean complete; + + //TODO: supply a real nonce-count, currently a server will interprete a repeated request as a replay + private static final String NC = "00000001"; //nonce-count is always 1 + private static final int QOP_MISSING = 0; + private static final int QOP_AUTH_INT = 1; + private static final int QOP_AUTH = 2; + + private int qopVariant = QOP_MISSING; + private String cnonce; + + /** + * Default constructor for the digest authetication scheme. + */ + public DigestScheme() { + super(); + this.complete = false; + } + + /** + * Processes the Digest challenge. + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + @Override + public void processChallenge( + final Header header) throws MalformedChallengeException { + super.processChallenge(header); + + if (getParameter("realm") == null) { + throw new MalformedChallengeException("missing realm in challange"); + } + if (getParameter("nonce") == null) { + throw new MalformedChallengeException("missing nonce in challange"); + } + + boolean unsupportedQop = false; + // qop parsing + String qop = getParameter("qop"); + if (qop != null) { + StringTokenizer tok = new StringTokenizer(qop,","); + while (tok.hasMoreTokens()) { + String variant = tok.nextToken().trim(); + if (variant.equals("auth")) { + qopVariant = QOP_AUTH; + break; //that's our favourite, because auth-int is unsupported + } else if (variant.equals("auth-int")) { + qopVariant = QOP_AUTH_INT; + } else { + unsupportedQop = true; + } + } + } + + if (unsupportedQop && (qopVariant == QOP_MISSING)) { + throw new MalformedChallengeException("None of the qop methods is supported"); + } + // Reset cnonce + this.cnonce = null; + this.complete = true; + } + + /** + * Tests if the Digest authentication process has been completed. + * + * @return <tt>true</tt> if Digest authorization has been processed, + * <tt>false</tt> otherwise. + */ + public boolean isComplete() { + String s = getParameter("stale"); + if ("true".equalsIgnoreCase(s)) { + return false; + } else { + return this.complete; + } + } + + /** + * Returns textual designation of the digest authentication scheme. + * + * @return <code>digest</code> + */ + public String getSchemeName() { + return "digest"; + } + + /** + * Returns <tt>false</tt>. Digest authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; + } + + public void overrideParamter(final String name, final String value) { + getParameters().put(name, value); + } + + private String getCnonce() { + if (this.cnonce == null) { + this.cnonce = createCnonce(); + } + return this.cnonce; + } + + /** + * Produces a digest authorization string for the given set of + * {@link Credentials}, method name and URI. + * + * @param credentials A set of credentials to be used for athentication + * @param request The request being authenticated + * + * @throws org.apache.http.auth.InvalidCredentialsException if authentication credentials + * are not valid or not applicable for this authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a digest authorization string + */ + public Header authenticate( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + + // Add method name and request-URI to the parameter map + getParameters().put("methodname", request.getRequestLine().getMethod()); + getParameters().put("uri", request.getRequestLine().getUri()); + String charset = getParameter("charset"); + if (charset == null) { + charset = AuthParams.getCredentialCharset(request.getParams()); + getParameters().put("charset", charset); + } + String digest = createDigest(credentials); + return createDigestHeader(credentials, digest); + } + + private static MessageDigest createMessageDigest( + final String digAlg) throws UnsupportedDigestAlgorithmException { + try { + return MessageDigest.getInstance(digAlg); + } catch (Exception e) { + throw new UnsupportedDigestAlgorithmException( + "Unsupported algorithm in HTTP Digest authentication: " + + digAlg); + } + } + + /** + * Creates an MD5 response digest. + * + * @return The created digest as string. This will be the response tag's + * value in the Authentication HTTP header. + * @throws AuthenticationException when MD5 is an unsupported algorithm + */ + private String createDigest(final Credentials credentials) throws AuthenticationException { + // Collecting required tokens + String uri = getParameter("uri"); + String realm = getParameter("realm"); + String nonce = getParameter("nonce"); + String method = getParameter("methodname"); + String algorithm = getParameter("algorithm"); + if (uri == null) { + throw new IllegalStateException("URI may not be null"); + } + if (realm == null) { + throw new IllegalStateException("Realm may not be null"); + } + if (nonce == null) { + throw new IllegalStateException("Nonce may not be null"); + } + // If an algorithm is not specified, default to MD5. + if (algorithm == null) { + algorithm = "MD5"; + } + // If an charset is not specified, default to ISO-8859-1. + String charset = getParameter("charset"); + if (charset == null) { + charset = "ISO-8859-1"; + } + + if (qopVariant == QOP_AUTH_INT) { + throw new AuthenticationException( + "Unsupported qop in HTTP Digest authentication"); + } + + MessageDigest md5Helper = createMessageDigest("MD5"); + + String uname = credentials.getUserPrincipal().getName(); + String pwd = credentials.getPassword(); + + // 3.2.2.2: Calculating digest + StringBuilder tmp = new StringBuilder(uname.length() + realm.length() + pwd.length() + 2); + tmp.append(uname); + tmp.append(':'); + tmp.append(realm); + tmp.append(':'); + tmp.append(pwd); + // unq(username-value) ":" unq(realm-value) ":" passwd + String a1 = tmp.toString(); + + //a1 is suitable for MD5 algorithm + if(algorithm.equals("MD5-sess")) { + // H( unq(username-value) ":" unq(realm-value) ":" passwd ) + // ":" unq(nonce-value) + // ":" unq(cnonce-value) + + String cnonce = getCnonce(); + + String tmp2=encode(md5Helper.digest(EncodingUtils.getBytes(a1, charset))); + StringBuilder tmp3 = new StringBuilder(tmp2.length() + nonce.length() + cnonce.length() + 2); + tmp3.append(tmp2); + tmp3.append(':'); + tmp3.append(nonce); + tmp3.append(':'); + tmp3.append(cnonce); + a1 = tmp3.toString(); + } else if (!algorithm.equals("MD5")) { + throw new AuthenticationException("Unhandled algorithm " + algorithm + " requested"); + } + String md5a1 = encode(md5Helper.digest(EncodingUtils.getBytes(a1, charset))); + + String a2 = null; + if (qopVariant == QOP_AUTH_INT) { + // Unhandled qop auth-int + //we do not have access to the entity-body or its hash + //TODO: add Method ":" digest-uri-value ":" H(entity-body) + } else { + a2 = method + ':' + uri; + } + String md5a2 = encode(md5Helper.digest(EncodingUtils.getAsciiBytes(a2))); + + // 3.2.2.1 + String serverDigestValue; + if (qopVariant == QOP_MISSING) { + StringBuilder tmp2 = new StringBuilder(md5a1.length() + nonce.length() + md5a2.length()); + tmp2.append(md5a1); + tmp2.append(':'); + tmp2.append(nonce); + tmp2.append(':'); + tmp2.append(md5a2); + serverDigestValue = tmp2.toString(); + } else { + String qopOption = getQopVariantString(); + String cnonce = getCnonce(); + + StringBuilder tmp2 = new StringBuilder(md5a1.length() + nonce.length() + + NC.length() + cnonce.length() + qopOption.length() + md5a2.length() + 5); + tmp2.append(md5a1); + tmp2.append(':'); + tmp2.append(nonce); + tmp2.append(':'); + tmp2.append(NC); + tmp2.append(':'); + tmp2.append(cnonce); + tmp2.append(':'); + tmp2.append(qopOption); + tmp2.append(':'); + tmp2.append(md5a2); + serverDigestValue = tmp2.toString(); + } + + String serverDigest = + encode(md5Helper.digest(EncodingUtils.getAsciiBytes(serverDigestValue))); + + return serverDigest; + } + + /** + * Creates digest-response header as defined in RFC2617. + * + * @param credentials User credentials + * @param digest The response tag's value as String. + * + * @return The digest-response as String. + */ + private Header createDigestHeader( + final Credentials credentials, + final String digest) throws AuthenticationException { + + CharArrayBuffer buffer = new CharArrayBuffer(128); + if (isProxy()) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": Digest "); + + String uri = getParameter("uri"); + String realm = getParameter("realm"); + String nonce = getParameter("nonce"); + String opaque = getParameter("opaque"); + String response = digest; + String algorithm = getParameter("algorithm"); + + String uname = credentials.getUserPrincipal().getName(); + + List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(20); + params.add(new BasicNameValuePair("username", uname)); + params.add(new BasicNameValuePair("realm", realm)); + params.add(new BasicNameValuePair("nonce", nonce)); + params.add(new BasicNameValuePair("uri", uri)); + params.add(new BasicNameValuePair("response", response)); + + if (qopVariant != QOP_MISSING) { + params.add(new BasicNameValuePair("qop", getQopVariantString())); + params.add(new BasicNameValuePair("nc", NC)); + params.add(new BasicNameValuePair("cnonce", getCnonce())); + } + if (algorithm != null) { + params.add(new BasicNameValuePair("algorithm", algorithm)); + } + if (opaque != null) { + params.add(new BasicNameValuePair("opaque", opaque)); + } + + for (int i = 0; i < params.size(); i++) { + BasicNameValuePair param = params.get(i); + if (i > 0) { + buffer.append(", "); + } + boolean noQuotes = "nc".equals(param.getName()) || + "qop".equals(param.getName()); + BasicHeaderValueFormatter.DEFAULT + .formatNameValuePair(buffer, param, !noQuotes); + } + return new BufferedHeader(buffer); + } + + private String getQopVariantString() { + String qopOption; + if (qopVariant == QOP_AUTH_INT) { + qopOption = "auth-int"; + } else { + qopOption = "auth"; + } + return qopOption; + } + + /** + * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long + * <CODE>String</CODE> according to RFC 2617. + * + * @param binaryData array containing the digest + * @return encoded MD5, or <CODE>null</CODE> if encoding failed + */ + private static String encode(byte[] binaryData) { + if (binaryData.length != 16) { + return null; + } + + char[] buffer = new char[32]; + for (int i = 0; i < 16; i++) { + int low = (binaryData[i] & 0x0f); + int high = ((binaryData[i] & 0xf0) >> 4); + buffer[i * 2] = HEXADECIMAL[high]; + buffer[(i * 2) + 1] = HEXADECIMAL[low]; + } + + return new String(buffer); + } + + + /** + * Creates a random cnonce value based on the current time. + * + * @return The cnonce value as String. + * @throws UnsupportedDigestAlgorithmException if MD5 algorithm is not supported. + */ + public static String createCnonce() { + String cnonce; + + MessageDigest md5Helper = createMessageDigest("MD5"); + + cnonce = Long.toString(System.currentTimeMillis()); + cnonce = encode(md5Helper.digest(EncodingUtils.getAsciiBytes(cnonce))); + + return cnonce; + } +} diff --git a/src/org/apache/http/impl/auth/DigestSchemeFactory.java b/src/org/apache/http/impl/auth/DigestSchemeFactory.java new file mode 100644 index 0000000..38f2e12 --- /dev/null +++ b/src/org/apache/http/impl/auth/DigestSchemeFactory.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/DigestSchemeFactory.java $ + * $Revision: 534839 $ + * $Date: 2007-05-03 06:03:41 -0700 (Thu, 03 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthSchemeFactory; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class DigestSchemeFactory implements AuthSchemeFactory { + + public AuthScheme newInstance(final HttpParams params) { + return new DigestScheme(); + } + +} diff --git a/src/org/apache/http/impl/auth/NTLMEngine.java b/src/org/apache/http/impl/auth/NTLMEngine.java new file mode 100644 index 0000000..7b6bf42 --- /dev/null +++ b/src/org/apache/http/impl/auth/NTLMEngine.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/NTLMEngine.java $ + * $Revision: 659788 $ + * $Date: 2008-05-24 03:42:23 -0700 (Sat, 24 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +/** + * Abstract NTLM authentication engine. The engine can be used to + * generate Type1 messages and Type3 messages in response to a + * Type2 challenge. + * <p/> + * For details see <a href="http://davenport.sourceforge.net/ntlm.html">this resource</a> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> +*/ +public interface NTLMEngine { + + /** + * Generates a Type1 message given the domain and workstation. + * + * @param domain Optional Windows domain name. Can be <code>null</code>. + * @param workstation Optional Windows workstation name. Can be + * <code>null</code>. + * @return Type1 message + * @throws NTLMEngineException + */ + String generateType1Msg( + String domain, + String workstation) throws NTLMEngineException; + + /** + * Generates a Type3 message given the user credentials and the + * authentication challenge. + * + * @param username Windows user name + * @param password Password + * @param domain Windows domain name + * @param workstation Windows workstation name + * @param challenge Type2 challenge. + * @return Type3 response. + * @throws NTLMEngineException + */ + String generateType3Msg( + String username, + String password, + String domain, + String workstation, + String challenge) throws NTLMEngineException; + +} diff --git a/src/org/apache/http/impl/auth/NTLMEngineException.java b/src/org/apache/http/impl/auth/NTLMEngineException.java new file mode 100644 index 0000000..73baabc --- /dev/null +++ b/src/org/apache/http/impl/auth/NTLMEngineException.java @@ -0,0 +1,70 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/NTLMEngineException.java $ + * $Revision: 655048 $ + * $Date: 2008-05-10 04:22:12 -0700 (Sat, 10 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.auth.AuthenticationException; + +/** + * Signals NTLM protocol failure. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class NTLMEngineException extends AuthenticationException { + + private static final long serialVersionUID = 6027981323731768824L; + + public NTLMEngineException() { + super(); + } + + /** + * Creates a new NTLMEngineException with the specified message. + * + * @param message the exception detail message + */ + public NTLMEngineException(String message) { + super(message); + } + + /** + * Creates a new NTLMEngineException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public NTLMEngineException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/org/apache/http/impl/auth/NTLMScheme.java b/src/org/apache/http/impl/auth/NTLMScheme.java new file mode 100644 index 0000000..8dfdbba --- /dev/null +++ b/src/org/apache/http/impl/auth/NTLMScheme.java @@ -0,0 +1,149 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/NTLMScheme.java $ + * $Revision: 655048 $ + * $Date: 2008-05-10 04:22:12 -0700 (Sat, 10 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.InvalidCredentialsException; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.auth.NTCredentials; +import org.apache.http.impl.auth.AuthSchemeBase; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; + +public class NTLMScheme extends AuthSchemeBase { + + enum State { + UNINITIATED, + CHALLENGE_RECEIVED, + MSG_TYPE1_GENERATED, + MSG_TYPE2_RECEVIED, + MSG_TYPE3_GENERATED, + FAILED, + } + + private final NTLMEngine engine; + + private State state; + private String challenge; + + public NTLMScheme(final NTLMEngine engine) { + super(); + if (engine == null) { + throw new IllegalArgumentException("NTLM engine may not be null"); + } + this.engine = engine; + this.state = State.UNINITIATED; + this.challenge = null; + } + + public String getSchemeName() { + return "ntlm"; + } + + public String getParameter(String name) { + // String parameters not supported + return null; + } + + public String getRealm() { + // NTLM does not support the concept of an authentication realm + return null; + } + + public boolean isConnectionBased() { + return true; + } + + @Override + protected void parseChallenge( + final CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException { + String challenge = buffer.substringTrimmed(pos, len); + if (challenge.length() == 0) { + if (this.state == State.UNINITIATED) { + this.state = State.CHALLENGE_RECEIVED; + } else { + this.state = State.FAILED; + } + this.challenge = null; + } else { + this.state = State.MSG_TYPE2_RECEVIED; + this.challenge = challenge; + } + } + + public Header authenticate( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + NTCredentials ntcredentials = null; + try { + ntcredentials = (NTCredentials) credentials; + } catch (ClassCastException e) { + throw new InvalidCredentialsException( + "Credentials cannot be used for NTLM authentication: " + + credentials.getClass().getName()); + } + String response = null; + if (this.state == State.CHALLENGE_RECEIVED || this.state == State.FAILED) { + response = this.engine.generateType1Msg( + ntcredentials.getDomain(), + ntcredentials.getWorkstation()); + this.state = State.MSG_TYPE1_GENERATED; + } else if (this.state == State.MSG_TYPE2_RECEVIED) { + response = this.engine.generateType3Msg( + ntcredentials.getUserName(), + ntcredentials.getPassword(), + ntcredentials.getDomain(), + ntcredentials.getWorkstation(), + this.challenge); + this.state = State.MSG_TYPE3_GENERATED; + } else { + throw new AuthenticationException("Unexpected state: " + this.state); + } + CharArrayBuffer buffer = new CharArrayBuffer(32); + if (isProxy()) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": NTLM "); + buffer.append(response); + return new BufferedHeader(buffer); + } + + public boolean isComplete() { + return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED; + } + +} diff --git a/src/org/apache/http/impl/auth/RFC2617Scheme.java b/src/org/apache/http/impl/auth/RFC2617Scheme.java new file mode 100644 index 0000000..0ed0a28 --- /dev/null +++ b/src/org/apache/http/impl/auth/RFC2617Scheme.java @@ -0,0 +1,119 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java $ + * $Revision: 659595 $ + * $Date: 2008-05-23 09:47:14 -0700 (Fri, 23 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.HeaderElement; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.message.BasicHeaderValueParser; +import org.apache.http.message.HeaderValueParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract authentication scheme class that lays foundation for all + * RFC 2617 compliant authetication schemes and provides capabilities common + * to all authentication schemes defined in RFC 2617. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> +*/ +public abstract class RFC2617Scheme extends AuthSchemeBase { + + /** + * Authentication parameter map. + */ + private Map<String, String> params; + + /** + * Default constructor for RFC2617 compliant authetication schemes. + */ + public RFC2617Scheme() { + super(); + } + + @Override + protected void parseChallenge( + final CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException { + HeaderValueParser parser = BasicHeaderValueParser.DEFAULT; + ParserCursor cursor = new ParserCursor(pos, buffer.length()); + HeaderElement[] elements = parser.parseElements(buffer, cursor); + if (elements.length == 0) { + throw new MalformedChallengeException("Authentication challenge is empty"); + } + + this.params = new HashMap<String, String>(elements.length); + for (HeaderElement element : elements) { + this.params.put(element.getName(), element.getValue()); + } + } + + /** + * Returns authentication parameters map. Keys in the map are lower-cased. + * + * @return the map of authentication parameters + */ + protected Map<String, String> getParameters() { + if (this.params == null) { + this.params = new HashMap<String, String>(); + } + return this.params; + } + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return the parameter with the given name + */ + public String getParameter(final String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + if (this.params == null) { + return null; + } + return this.params.get(name.toLowerCase(Locale.ENGLISH)); + } + + /** + * Returns authentication realm. The realm may not be null. + * + * @return the authentication realm + */ + public String getRealm() { + return getParameter("realm"); + } + +} diff --git a/src/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java b/src/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java new file mode 100644 index 0000000..abd0a66 --- /dev/null +++ b/src/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java $ + * $Revision: 527479 $ + * $Date: 2007-04-11 05:55:12 -0700 (Wed, 11 Apr 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +/** + * Authentication credentials required to respond to a authentication + * challenge are invalid + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class UnsupportedDigestAlgorithmException extends RuntimeException { + + private static final long serialVersionUID = 319558534317118022L; + + /** + * Creates a new UnsupportedAuthAlgoritmException with a <tt>null</tt> detail message. + */ + public UnsupportedDigestAlgorithmException() { + super(); + } + + /** + * Creates a new UnsupportedAuthAlgoritmException with the specified message. + * + * @param message the exception detail message + */ + public UnsupportedDigestAlgorithmException(String message) { + super(message); + } + + /** + * Creates a new UnsupportedAuthAlgoritmException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public UnsupportedDigestAlgorithmException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/impl/auth/package.html b/src/org/apache/http/impl/auth/package.html new file mode 100644 index 0000000..e301283 --- /dev/null +++ b/src/org/apache/http/impl/auth/package.html @@ -0,0 +1,4 @@ +<body> + +</body> + diff --git a/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java b/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java new file mode 100644 index 0000000..57699d5 --- /dev/null +++ b/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java @@ -0,0 +1,165 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractAuthenticationHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.CharArrayBuffer; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public abstract class AbstractAuthenticationHandler implements AuthenticationHandler { + + private final Log log = LogFactory.getLog(getClass()); + + private static final List<String> DEFAULT_SCHEME_PRIORITY = Arrays.asList(new String[] { + "ntlm", + "digest", + "basic" + }); + + public AbstractAuthenticationHandler() { + super(); + } + + protected Map<String, Header> parseChallenges( + final Header[] headers) throws MalformedChallengeException { + + Map<String, Header> map = new HashMap<String, Header>(headers.length); + for (Header header : headers) { + CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int endIndex = pos; + String s = buffer.substring(beginIndex, endIndex); + map.put(s.toLowerCase(Locale.ENGLISH), header); + } + return map; + } + + protected List<String> getAuthPreferences() { + return DEFAULT_SCHEME_PRIORITY; + } + + public AuthScheme selectScheme( + final Map<String, Header> challenges, + final HttpResponse response, + final HttpContext context) throws AuthenticationException { + + AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute( + ClientContext.AUTHSCHEME_REGISTRY); + if (registry == null) { + throw new IllegalStateException("AuthScheme registry not set in HTTP context"); + } + + List<?> authPrefs = (List<?>) context.getAttribute( + ClientContext.AUTH_SCHEME_PREF); + if (authPrefs == null) { + authPrefs = getAuthPreferences(); + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication schemes in the order of preference: " + + authPrefs); + } + + AuthScheme authScheme = null; + for (int i = 0; i < authPrefs.size(); i++) { + String id = (String) authPrefs.get(i); + Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + + if (challenge != null) { + if (this.log.isDebugEnabled()) { + this.log.debug(id + " authentication scheme selected"); + } + try { + authScheme = registry.getAuthScheme(id, response.getParams()); + break; + } catch (IllegalStateException e) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication scheme " + id + " not supported"); + // Try again + } + } + } else { + if (this.log.isDebugEnabled()) { + this.log.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + if (authScheme == null) { + // If none selected, something is wrong + throw new AuthenticationException( + "Unable to respond to any of these challenges: " + + challenges); + } + return authScheme; + } + +} diff --git a/src/org/apache/http/impl/client/AbstractHttpClient.java b/src/org/apache/http/impl/client/AbstractHttpClient.java new file mode 100644 index 0000000..3a1b838 --- /dev/null +++ b/src/org/apache/http/impl/client/AbstractHttpClient.java @@ -0,0 +1,697 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java $ + * $Revision: 677250 $ + * $Date: 2008-07-16 04:45:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; +import java.net.URI; +import java.lang.reflect.UndeclaredThrowableException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpEntity; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.RequestDirector; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.DefaultedHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpProcessor; +import org.apache.http.protocol.HttpRequestExecutor; + +/** + * Convenience base class for HTTP client implementations. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 677250 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpClient implements HttpClient { + + private final Log log = LogFactory.getLog(getClass()); + + /** The parameters. */ + private HttpParams defaultParams; + + /** The request executor. */ + private HttpRequestExecutor requestExec; + + /** The connection manager. */ + private ClientConnectionManager connManager; + + /** The connection re-use strategy. */ + private ConnectionReuseStrategy reuseStrategy; + + /** The connection keep-alive strategy. */ + private ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The cookie spec registry. */ + private CookieSpecRegistry supportedCookieSpecs; + + /** The authentication scheme registry. */ + private AuthSchemeRegistry supportedAuthSchemes; + + /** The HTTP processor. */ + private BasicHttpProcessor httpProcessor; + + /** The request retry handler. */ + private HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + private RedirectHandler redirectHandler; + + /** The target authentication handler. */ + private AuthenticationHandler targetAuthHandler; + + /** The proxy authentication handler. */ + private AuthenticationHandler proxyAuthHandler; + + /** The cookie store. */ + private CookieStore cookieStore; + + /** The credentials provider. */ + private CredentialsProvider credsProvider; + + /** The route planner. */ + private HttpRoutePlanner routePlanner; + + /** The user token handler. */ + private UserTokenHandler userTokenHandler; + + + /** + * Creates a new HTTP client. + * + * @param conman the connection manager + * @param params the parameters + */ + protected AbstractHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + defaultParams = params; + connManager = conman; + } // constructor + + protected abstract HttpParams createHttpParams(); + + + protected abstract HttpContext createHttpContext(); + + + protected abstract HttpRequestExecutor createRequestExecutor(); + + + protected abstract ClientConnectionManager createClientConnectionManager(); + + + protected abstract AuthSchemeRegistry createAuthSchemeRegistry(); + + + protected abstract CookieSpecRegistry createCookieSpecRegistry(); + + + protected abstract ConnectionReuseStrategy createConnectionReuseStrategy(); + + + protected abstract ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy(); + + + protected abstract BasicHttpProcessor createHttpProcessor(); + + + protected abstract HttpRequestRetryHandler createHttpRequestRetryHandler(); + + + protected abstract RedirectHandler createRedirectHandler(); + + + protected abstract AuthenticationHandler createTargetAuthenticationHandler(); + + + protected abstract AuthenticationHandler createProxyAuthenticationHandler(); + + + protected abstract CookieStore createCookieStore(); + + + protected abstract CredentialsProvider createCredentialsProvider(); + + + protected abstract HttpRoutePlanner createHttpRoutePlanner(); + + + protected abstract UserTokenHandler createUserTokenHandler(); + + + // non-javadoc, see interface HttpClient + public synchronized final HttpParams getParams() { + if (defaultParams == null) { + defaultParams = createHttpParams(); + } + return defaultParams; + } + + + /** + * Replaces the parameters. + * The implementation here does not update parameters of dependent objects. + * + * @param params the new default parameters + */ + public synchronized void setParams(HttpParams params) { + defaultParams = params; + } + + + public synchronized final ClientConnectionManager getConnectionManager() { + if (connManager == null) { + connManager = createClientConnectionManager(); + } + return connManager; + } + + + public synchronized final HttpRequestExecutor getRequestExecutor() { + if (requestExec == null) { + requestExec = createRequestExecutor(); + } + return requestExec; + } + + + public synchronized final AuthSchemeRegistry getAuthSchemes() { + if (supportedAuthSchemes == null) { + supportedAuthSchemes = createAuthSchemeRegistry(); + } + return supportedAuthSchemes; + } + + + public synchronized void setAuthSchemes(final AuthSchemeRegistry authSchemeRegistry) { + supportedAuthSchemes = authSchemeRegistry; + } + + + public synchronized final CookieSpecRegistry getCookieSpecs() { + if (supportedCookieSpecs == null) { + supportedCookieSpecs = createCookieSpecRegistry(); + } + return supportedCookieSpecs; + } + + + public synchronized void setCookieSpecs(final CookieSpecRegistry cookieSpecRegistry) { + supportedCookieSpecs = cookieSpecRegistry; + } + + + public synchronized final ConnectionReuseStrategy getConnectionReuseStrategy() { + if (reuseStrategy == null) { + reuseStrategy = createConnectionReuseStrategy(); + } + return reuseStrategy; + } + + + public synchronized void setReuseStrategy(final ConnectionReuseStrategy reuseStrategy) { + this.reuseStrategy = reuseStrategy; + } + + + public synchronized final ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() { + if (keepAliveStrategy == null) { + keepAliveStrategy = createConnectionKeepAliveStrategy(); + } + return keepAliveStrategy; + } + + + public synchronized void setKeepAliveStrategy(final ConnectionKeepAliveStrategy keepAliveStrategy) { + this.keepAliveStrategy = keepAliveStrategy; + } + + + public synchronized final HttpRequestRetryHandler getHttpRequestRetryHandler() { + if (retryHandler == null) { + retryHandler = createHttpRequestRetryHandler(); + } + return retryHandler; + } + + + public synchronized void setHttpRequestRetryHandler(final HttpRequestRetryHandler retryHandler) { + this.retryHandler = retryHandler; + } + + + public synchronized final RedirectHandler getRedirectHandler() { + if (redirectHandler == null) { + redirectHandler = createRedirectHandler(); + } + return redirectHandler; + } + + + public synchronized void setRedirectHandler(final RedirectHandler redirectHandler) { + this.redirectHandler = redirectHandler; + } + + + public synchronized final AuthenticationHandler getTargetAuthenticationHandler() { + if (targetAuthHandler == null) { + targetAuthHandler = createTargetAuthenticationHandler(); + } + return targetAuthHandler; + } + + + public synchronized void setTargetAuthenticationHandler( + final AuthenticationHandler targetAuthHandler) { + this.targetAuthHandler = targetAuthHandler; + } + + + public synchronized final AuthenticationHandler getProxyAuthenticationHandler() { + if (proxyAuthHandler == null) { + proxyAuthHandler = createProxyAuthenticationHandler(); + } + return proxyAuthHandler; + } + + + public synchronized void setProxyAuthenticationHandler( + final AuthenticationHandler proxyAuthHandler) { + this.proxyAuthHandler = proxyAuthHandler; + } + + + public synchronized final CookieStore getCookieStore() { + if (cookieStore == null) { + cookieStore = createCookieStore(); + } + return cookieStore; + } + + + public synchronized void setCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + + public synchronized final CredentialsProvider getCredentialsProvider() { + if (credsProvider == null) { + credsProvider = createCredentialsProvider(); + } + return credsProvider; + } + + + public synchronized void setCredentialsProvider(final CredentialsProvider credsProvider) { + this.credsProvider = credsProvider; + } + + + public synchronized final HttpRoutePlanner getRoutePlanner() { + if (this.routePlanner == null) { + this.routePlanner = createHttpRoutePlanner(); + } + return this.routePlanner; + } + + + public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + } + + + public synchronized final UserTokenHandler getUserTokenHandler() { + if (this.userTokenHandler == null) { + this.userTokenHandler = createUserTokenHandler(); + } + return this.userTokenHandler; + } + + + public synchronized void setUserTokenHandler(final UserTokenHandler userTokenHandler) { + this.userTokenHandler = userTokenHandler; + } + + + protected synchronized final BasicHttpProcessor getHttpProcessor() { + if (httpProcessor == null) { + httpProcessor = createHttpProcessor(); + } + return httpProcessor; + } + + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + } + + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp, int index) { + getHttpProcessor().addInterceptor(itcp, index); + } + + + public synchronized HttpResponseInterceptor getResponseInterceptor(int index) { + return getHttpProcessor().getResponseInterceptor(index); + } + + + public synchronized int getResponseInterceptorCount() { + return getHttpProcessor().getResponseInterceptorCount(); + } + + + public synchronized void clearResponseInterceptors() { + getHttpProcessor().clearResponseInterceptors(); + } + + + public void removeResponseInterceptorByClass(Class<? extends HttpResponseInterceptor> clazz) { + getHttpProcessor().removeResponseInterceptorByClass(clazz); + } + + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + } + + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp, int index) { + getHttpProcessor().addInterceptor(itcp, index); + } + + + public synchronized HttpRequestInterceptor getRequestInterceptor(int index) { + return getHttpProcessor().getRequestInterceptor(index); + } + + + public synchronized int getRequestInterceptorCount() { + return getHttpProcessor().getRequestInterceptorCount(); + } + + + public synchronized void clearRequestInterceptors() { + getHttpProcessor().clearRequestInterceptors(); + } + + + public void removeRequestInterceptorByClass(Class<? extends HttpRequestInterceptor> clazz) { + getHttpProcessor().removeRequestInterceptorByClass(clazz); + } + + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpUriRequest request) + throws IOException, ClientProtocolException { + + return execute(request, (HttpContext) null); + } + + + /** + * Maps to {@link HttpClient#execute(HttpHost,HttpRequest,HttpContext) + * execute(target, request, context)}. + * The target is determined from the URI of the request. + * + * @param request the request to execute + * @param context the request-specific execution context, + * or <code>null</code> to use a default context + */ + public final HttpResponse execute(HttpUriRequest request, + HttpContext context) + throws IOException, ClientProtocolException { + + if (request == null) { + throw new IllegalArgumentException + ("Request must not be null."); + } + + return execute(determineTarget(request), request, context); + } + + private HttpHost determineTarget(HttpUriRequest request) { + // A null target may be acceptable if there is a default target. + // Otherwise, the null target is detected in the director. + HttpHost target = null; + + URI requestURI = request.getURI(); + if (requestURI.isAbsolute()) { + target = new HttpHost( + requestURI.getHost(), + requestURI.getPort(), + requestURI.getScheme()); + } + return target; + } + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpHost target, HttpRequest request) + throws IOException, ClientProtocolException { + + return execute(target, request, (HttpContext) null); + } + + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) + throws IOException, ClientProtocolException { + + if (request == null) { + throw new IllegalArgumentException + ("Request must not be null."); + } + // a null target may be acceptable, this depends on the route planner + // a null context is acceptable, default context created below + + HttpContext execContext = null; + RequestDirector director = null; + + // Initialize the request execution context making copies of + // all shared objects that are potentially threading unsafe. + synchronized (this) { + + HttpContext defaultContext = createHttpContext(); + if (context == null) { + execContext = defaultContext; + } else { + execContext = new DefaultedHttpContext(context, defaultContext); + } + // Create a director for this request + director = createClientRequestDirector( + getRequestExecutor(), + getConnectionManager(), + getConnectionReuseStrategy(), + getConnectionKeepAliveStrategy(), + getRoutePlanner(), + getHttpProcessor().copy(), + getHttpRequestRetryHandler(), + getRedirectHandler(), + getTargetAuthenticationHandler(), + getProxyAuthenticationHandler(), + getUserTokenHandler(), + determineParams(request)); + } + + try { + return director.execute(target, request, execContext); + } catch(HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } // execute + + + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler stateHandler, + final HttpParams params) { + return new DefaultRequestDirector( + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectHandler, + targetAuthHandler, + proxyAuthHandler, + stateHandler, + params); + } + + /** + * Obtains parameters for executing a request. + * The default implementation in this class creates a new + * {@link ClientParamsStack} from the request parameters + * and the client parameters. + * <br/> + * This method is called by the default implementation of + * {@link #execute(HttpHost,HttpRequest,HttpContext)} + * to obtain the parameters for the + * {@link DefaultRequestDirector}. + * + * @param req the request that will be executed + * + * @return the parameters to use + */ + protected HttpParams determineParams(HttpRequest req) { + return new ClientParamsStack + (null, getParams(), req.getParams(), null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException { + return execute(request, responseHandler, null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, + final HttpContext context) + throws IOException, ClientProtocolException { + HttpHost target = determineTarget(request); + return execute(target, request, responseHandler, context); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpHost target, + final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException { + return execute(target, request, responseHandler, null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpHost target, + final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, + final HttpContext context) + throws IOException, ClientProtocolException { + if (responseHandler == null) { + throw new IllegalArgumentException + ("Response handler must not be null."); + } + + HttpResponse response = execute(target, request, context); + + T result; + try { + result = responseHandler.handleResponse(response); + } catch (Throwable t) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + try { + entity.consumeContent(); + } catch (Throwable t2) { + // Log this exception. The original exception is more + // important and will be thrown to the caller. + this.log.warn("Error consuming content after an exception.", t2); + } + } + + if (t instanceof Error) { + throw (Error) t; + } + + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + + if (t instanceof IOException) { + throw (IOException) t; + } + + throw new UndeclaredThrowableException(t); + } + + // Handling the response was successful. Ensure that the content has + // been fully consumed. + HttpEntity entity = response.getEntity(); + if (entity != null) { + // Let this exception go to the caller. + entity.consumeContent(); + } + + return result; + } + + +} // class AbstractHttpClient diff --git a/src/org/apache/http/impl/client/BasicCookieStore.java b/src/org/apache/http/impl/client/BasicCookieStore.java new file mode 100644 index 0000000..9970961 --- /dev/null +++ b/src/org/apache/http/impl/client/BasicCookieStore.java @@ -0,0 +1,162 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicCookieStore.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.apache.http.client.CookieStore; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieIdentityComparator; + +/** + * Default implementation of {@link CookieStore} + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Sean C. Sullivan + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:adrian@intencha.com">Adrian Sutton</a> + * + * @since 4.0 + */ +public class BasicCookieStore implements CookieStore { + + private final ArrayList<Cookie> cookies; + + private final Comparator<Cookie> cookieComparator; + + // -------------------------------------------------------- Class Variables + + /** + * Default constructor. + */ + public BasicCookieStore() { + super(); + this.cookies = new ArrayList<Cookie>(); + this.cookieComparator = new CookieIdentityComparator(); + } + + /** + * Adds an {@link Cookie HTTP cookie}, replacing any existing equivalent cookies. + * If the given cookie has already expired it will not be added, but existing + * values will still be removed. + * + * @param cookie the {@link Cookie cookie} to be added + * + * @see #addCookies(Cookie[]) + * + */ + public synchronized void addCookie(Cookie cookie) { + if (cookie != null) { + // first remove any old cookie that is equivalent + for (Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (cookieComparator.compare(cookie, it.next()) == 0) { + it.remove(); + break; + } + } + if (!cookie.isExpired(new Date())) { + cookies.add(cookie); + } + } + } + + /** + * Adds an array of {@link Cookie HTTP cookies}. Cookies are added individually and + * in the given array order. If any of the given cookies has already expired it will + * not be added, but existing values will still be removed. + * + * @param cookies the {@link Cookie cookies} to be added + * + * @see #addCookie(Cookie) + * + */ + public synchronized void addCookies(Cookie[] cookies) { + if (cookies != null) { + for (Cookie cooky : cookies) { + this.addCookie(cooky); + } + } + } + + /** + * Returns an immutable array of {@link Cookie cookies} that this HTTP + * state currently contains. + * + * @return an array of {@link Cookie cookies}. + */ + public synchronized List<Cookie> getCookies() { + return Collections.unmodifiableList(this.cookies); + } + + /** + * Removes all of {@link Cookie cookies} in this HTTP state + * that have expired by the specified {@link java.util.Date date}. + * + * @return true if any cookies were purged. + * + * @see Cookie#isExpired(Date) + */ + public synchronized boolean clearExpired(final Date date) { + if (date == null) { + return false; + } + boolean removed = false; + for (Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (it.next().isExpired(date)) { + it.remove(); + removed = true; + } + } + return removed; + } + + @Override + public String toString() { + return cookies.toString(); + } + + /** + * Clears all cookies. + */ + public synchronized void clear() { + cookies.clear(); + } + +} diff --git a/src/org/apache/http/impl/client/BasicCredentialsProvider.java b/src/org/apache/http/impl/client/BasicCredentialsProvider.java new file mode 100644 index 0000000..02427ea --- /dev/null +++ b/src/org/apache/http/impl/client/BasicCredentialsProvider.java @@ -0,0 +1,143 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicCredentialsProvider.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.HashMap; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; + +/** + * Default implementation of {@link CredentialsProvider} + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Sean C. Sullivan + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:adrian@intencha.com">Adrian Sutton</a> + * + * @since 4.0 + */ +public class BasicCredentialsProvider implements CredentialsProvider { + + private final HashMap<AuthScope, Credentials> credMap; + + /** + * Default constructor. + */ + public BasicCredentialsProvider() { + super(); + this.credMap = new HashMap<AuthScope, Credentials>(); + } + + /** + * Sets the {@link Credentials credentials} for the given authentication + * scope. Any previous credentials for the given scope will be overwritten. + * + * @param authscope the {@link AuthScope authentication scope} + * @param credentials the authentication {@link Credentials credentials} + * for the given scope. + * + * @see #getCredentials(AuthScope) + */ + public synchronized void setCredentials( + final AuthScope authscope, + final Credentials credentials) { + if (authscope == null) { + throw new IllegalArgumentException("Authentication scope may not be null"); + } + credMap.put(authscope, credentials); + } + + /** + * Find matching {@link Credentials credentials} for the given authentication scope. + * + * @param map the credentials hash map + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + */ + private static Credentials matchCredentials( + final HashMap<AuthScope, Credentials> map, + final AuthScope authscope) { + // see if we get a direct hit + Credentials creds = map.get(authscope); + if (creds == null) { + // Nope. + // Do a full scan + int bestMatchFactor = -1; + AuthScope bestMatch = null; + for (AuthScope current: map.keySet()) { + int factor = authscope.match(current); + if (factor > bestMatchFactor) { + bestMatchFactor = factor; + bestMatch = current; + } + } + if (bestMatch != null) { + creds = map.get(bestMatch); + } + } + return creds; + } + + /** + * Get the {@link Credentials credentials} for the given authentication scope. + * + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + * @see #setCredentials(AuthScope, Credentials) + */ + public synchronized Credentials getCredentials(final AuthScope authscope) { + if (authscope == null) { + throw new IllegalArgumentException("Authentication scope may not be null"); + } + return matchCredentials(this.credMap, authscope); + } + + @Override + public String toString() { + return credMap.toString(); + } + + /** + * Clears all credentials. + */ + public synchronized void clear() { + this.credMap.clear(); + } + +} diff --git a/src/org/apache/http/impl/client/BasicResponseHandler.java b/src/org/apache/http/impl/client/BasicResponseHandler.java new file mode 100644 index 0000000..f17d30d --- /dev/null +++ b/src/org/apache/http/impl/client/BasicResponseHandler.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicResponseHandler.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.HttpResponseException; +import org.apache.http.util.EntityUtils; + +/** + * A {@link ResponseHandler} that returns the response body as a String + * for successful (2xx) responses. If the response code was >= 300, the response + * body is consumed and an {@link HttpResponseException} is thrown. + * + * If this is used with + * {@link org.apache.http.client.HttpClient#execute( + * org.apache.http.client.methods.HttpUriRequest, ResponseHandler), + * HttpClient may handle redirects (3xx responses) internally. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 677240 $ + * + * @since 4.0 + */ +public class BasicResponseHandler implements ResponseHandler<String> { + + /** + * Returns the response body as a String if the response was successful (a + * 2xx status code). If no response body exists, this returns null. If the + * response was unsuccessful (>= 300 status code), throws an + * {@link HttpResponseException}. + */ + public String handleResponse(final HttpResponse response) + throws HttpResponseException, IOException { + StatusLine statusLine = response.getStatusLine(); + if (statusLine.getStatusCode() >= 300) { + throw new HttpResponseException(statusLine.getStatusCode(), + statusLine.getReasonPhrase()); + } + + HttpEntity entity = response.getEntity(); + return entity == null ? null : EntityUtils.toString(entity); + } + +} diff --git a/src/org/apache/http/impl/client/ClientParamsStack.java b/src/org/apache/http/impl/client/ClientParamsStack.java new file mode 100644 index 0000000..a017e5d --- /dev/null +++ b/src/org/apache/http/impl/client/ClientParamsStack.java @@ -0,0 +1,282 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/ClientParamsStack.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.params.HttpParams; +import org.apache.http.params.AbstractHttpParams; + + +/** + * Represents a stack of parameter collections. + * When retrieving a parameter, the stack is searched in a fixed order + * and the first match returned. Setting parameters via the stack is + * not supported. To minimize overhead, the stack has a fixed size and + * does not maintain an internal array. + * The supported stack entries, sorted by increasing priority, are: + * <ol> + * <li>Application parameters: + * expected to be the same for all clients used by an application. + * These provide "global", that is application-wide, defaults. + * </li> + * <li>Client parameters: + * specific to an instance of + * {@link org.apache.http.client.HttpClient HttpClient}. + * These provide client specific defaults. + * </li> + * <li>Request parameters: + * specific to a single request execution. + * For overriding client and global defaults. + * </li> + * <li>Override parameters: + * specific to an instance of + * {@link org.apache.http.client.HttpClient HttpClient}. + * These can be used to set parameters that cannot be overridden + * on a per-request basis. + * </li> + * </ol> + * Each stack entry may be <code>null</code>. That is preferable over + * an empty params collection, since it avoids searching the empty collection + * when looking up parameters. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * @version $Revision: 673450 $ + */ +public class ClientParamsStack extends AbstractHttpParams { + + private final Log log = LogFactory.getLog(getClass()); + + /** The application parameter collection, or <code>null</code>. */ + protected final HttpParams applicationParams; + + /** The client parameter collection, or <code>null</code>. */ + protected final HttpParams clientParams; + + /** The request parameter collection, or <code>null</code>. */ + protected final HttpParams requestParams; + + /** The override parameter collection, or <code>null</code>. */ + protected final HttpParams overrideParams; + + + /** + * Creates a new parameter stack from elements. + * The arguments will be stored as-is, there is no copying to + * prevent modification. + * + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(HttpParams aparams, HttpParams cparams, + HttpParams rparams, HttpParams oparams) { + applicationParams = aparams; + clientParams = cparams; + requestParams = rparams; + overrideParams = oparams; + } + + + /** + * Creates a copy of a parameter stack. + * The new stack will have the exact same entries as the argument stack. + * There is no copying of parameters. + * + * @param stack the stack to copy + */ + public ClientParamsStack(ClientParamsStack stack) { + this(stack.getApplicationParams(), + stack.getClientParams(), + stack.getRequestParams(), + stack.getOverrideParams()); + } + + + /** + * Creates a modified copy of a parameter stack. + * The new stack will contain the explicitly passed elements. + * For elements where the explicit argument is <code>null</code>, + * the corresponding element from the argument stack is used. + * There is no copying of parameters. + * + * @param stack the stack to modify + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(ClientParamsStack stack, + HttpParams aparams, HttpParams cparams, + HttpParams rparams, HttpParams oparams) { + this((aparams != null) ? aparams : stack.getApplicationParams(), + (cparams != null) ? cparams : stack.getClientParams(), + (rparams != null) ? rparams : stack.getRequestParams(), + (oparams != null) ? oparams : stack.getOverrideParams()); + } + + + /** + * Obtains the application parameters of this stack. + * + * @return the application parameters, or <code>null</code> + */ + public final HttpParams getApplicationParams() { + return applicationParams; + } + + /** + * Obtains the client parameters of this stack. + * + * @return the client parameters, or <code>null</code> + */ + public final HttpParams getClientParams() { + return clientParams; + } + + /** + * Obtains the request parameters of this stack. + * + * @return the request parameters, or <code>null</code> + */ + public final HttpParams getRequestParams() { + return requestParams; + } + + /** + * Obtains the override parameters of this stack. + * + * @return the override parameters, or <code>null</code> + */ + public final HttpParams getOverrideParams() { + return overrideParams; + } + + + /** + * Obtains a parameter from this stack. + * See class comment for search order. + * + * @param name the name of the parameter to obtain + * + * @return the highest-priority value for that parameter, or + * <code>null</code> if it is not set anywhere in this stack + */ + public Object getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException + ("Parameter name must not be null."); + } + + Object result = null; + + if (overrideParams != null) { + result = overrideParams.getParameter(name); + } + if ((result == null) && (requestParams != null)) { + result = requestParams.getParameter(name); + } + if ((result == null) && (clientParams != null)) { + result = clientParams.getParameter(name); + } + if ((result == null) && (applicationParams != null)) { + result = applicationParams.getParameter(name); + } + if (this.log.isDebugEnabled()) { + this.log.debug("'" + name + "': " + result); + } + + return result; + } + + /** + * Does <i>not</i> set a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * @param value ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public HttpParams setParameter(String name, Object value) + throws UnsupportedOperationException { + + throw new UnsupportedOperationException + ("Setting parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> remove a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public boolean removeParameter(String name) { + throw new UnsupportedOperationException + ("Removing parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> copy parameters. + * Parameter stacks are lightweight objects, expected to be instantiated + * as needed and to be used only in a very specific context. On top of + * that, they are read-only. The typical copy operation to prevent + * accidental modification of parameters passed by the application to + * a framework object is therefore pointless and disabled. + * Create a new stack if you really need a copy. + * <br/> + * Derived classes may change this behavior. + * + * @return <code>this</code> parameter stack + */ + public HttpParams copy() { + return this; + } + + +} diff --git a/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java b/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java new file mode 100644 index 0000000..c7641d2 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.client; + +import org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.HttpResponse; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.message.BasicHeaderElementIterator; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; + +/** + * Default implementation of a strategy deciding duration + * that a connection can remain idle. + * + * The default implementation looks solely at the 'Keep-Alive' + * header's timeout token. + * + * @author <a href="mailto:sberlin at gmail.com">Sam Berlin</a> + * + * @version $Revision: $ + * + * @since 4.0 + */ +public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { + + public long getKeepAliveDuration(HttpResponse response, HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + HeaderElementIterator it = new BasicHeaderElementIterator( + response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + while (it.hasNext()) { + HeaderElement he = it.nextElement(); + String param = he.getName(); + String value = he.getValue(); + if (value != null && param.equalsIgnoreCase("timeout")) { + try { + return Long.parseLong(value) * 1000; + } catch(NumberFormatException ignore) { + } + } + } + return -1; + } + +} diff --git a/src/org/apache/http/impl/client/DefaultHttpClient.java b/src/org/apache/http/impl/client/DefaultHttpClient.java new file mode 100644 index 0000000..f0b694e --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultHttpClient.java @@ -0,0 +1,332 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java $ + * $Revision: 677250 $ + * $Date: 2008-07-16 04:45:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpVersion; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.params.AuthPolicy; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.client.protocol.RequestAddCookies; +import org.apache.http.client.protocol.RequestDefaultHeaders; +import org.apache.http.client.protocol.RequestProxyAuthentication; +import org.apache.http.client.protocol.RequestTargetAuthentication; +import org.apache.http.client.protocol.ResponseProcessCookies; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionManagerFactory; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.impl.DefaultConnectionReuseStrategy; +import org.apache.http.impl.auth.BasicSchemeFactory; +import org.apache.http.impl.auth.DigestSchemeFactory; +import org.apache.http.impl.conn.DefaultHttpRoutePlanner; +import org.apache.http.impl.conn.SingleClientConnManager; +import org.apache.http.impl.cookie.BestMatchSpecFactory; +import org.apache.http.impl.cookie.BrowserCompatSpecFactory; +import org.apache.http.impl.cookie.NetscapeDraftSpecFactory; +import org.apache.http.impl.cookie.RFC2109SpecFactory; +import org.apache.http.impl.cookie.RFC2965SpecFactory; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestExecutor; +import org.apache.http.protocol.RequestConnControl; +import org.apache.http.protocol.RequestContent; +import org.apache.http.protocol.RequestExpectContinue; +import org.apache.http.protocol.RequestTargetHost; +import org.apache.http.protocol.RequestUserAgent; +import org.apache.http.util.VersionInfo; + + + +/** + * Default implementation of an HTTP client. + * <br/> + * This class replaces <code>HttpClient</code> in HttpClient 3. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 677250 $ + * + * @since 4.0 + */ +public class DefaultHttpClient extends AbstractHttpClient { + + + /** + * Creates a new HTTP client from parameters and a connection manager. + * + * @param params the parameters + * @param conman the connection manager + */ + public DefaultHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + super(conman, params); + } + + + public DefaultHttpClient(final HttpParams params) { + super(null, params); + } + + + public DefaultHttpClient() { + super(null, null); + } + + + @Override + protected HttpParams createHttpParams() { + HttpParams params = new BasicHttpParams(); + HttpProtocolParams.setVersion(params, + HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(params, + HTTP.DEFAULT_CONTENT_CHARSET); + HttpProtocolParams.setUseExpectContinue(params, + true); + + // determine the release version from packaged version info + final VersionInfo vi = VersionInfo.loadVersionInfo + ("org.apache.http.client", getClass().getClassLoader()); + final String release = (vi != null) ? + vi.getRelease() : VersionInfo.UNAVAILABLE; + HttpProtocolParams.setUserAgent(params, + "Apache-HttpClient/" + release + " (java 1.4)"); + + return params; + } + + + @Override + protected HttpRequestExecutor createRequestExecutor() { + return new HttpRequestExecutor(); + } + + + @Override + protected ClientConnectionManager createClientConnectionManager() { + SchemeRegistry registry = new SchemeRegistry(); + registry.register( + new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + registry.register( + new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); + + ClientConnectionManager connManager = null; + HttpParams params = getParams(); + + ClientConnectionManagerFactory factory = null; + + // Try first getting the factory directly as an object. + factory = (ClientConnectionManagerFactory) params + .getParameter(ClientPNames.CONNECTION_MANAGER_FACTORY); + if (factory == null) { // then try getting its class name. + String className = (String) params.getParameter( + ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME); + if (className != null) { + try { + Class<?> clazz = Class.forName(className); + factory = (ClientConnectionManagerFactory) clazz.newInstance(); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException("Invalid class name: " + className); + } catch (IllegalAccessException ex) { + throw new IllegalAccessError(ex.getMessage()); + } catch (InstantiationException ex) { + throw new InstantiationError(ex.getMessage()); + } + } + } + + if(factory != null) { + connManager = factory.newInstance(params, registry); + } else { + connManager = new SingleClientConnManager(getParams(), registry); + } + + return connManager; + } + + + @Override + protected HttpContext createHttpContext() { + HttpContext context = new BasicHttpContext(); + context.setAttribute( + ClientContext.AUTHSCHEME_REGISTRY, + getAuthSchemes()); + context.setAttribute( + ClientContext.COOKIESPEC_REGISTRY, + getCookieSpecs()); + context.setAttribute( + ClientContext.COOKIE_STORE, + getCookieStore()); + context.setAttribute( + ClientContext.CREDS_PROVIDER, + getCredentialsProvider()); + return context; + } + + + @Override + protected ConnectionReuseStrategy createConnectionReuseStrategy() { + return new DefaultConnectionReuseStrategy(); + } + + @Override + protected ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy() { + return new DefaultConnectionKeepAliveStrategy(); + } + + + @Override + protected AuthSchemeRegistry createAuthSchemeRegistry() { + AuthSchemeRegistry registry = new AuthSchemeRegistry(); + registry.register( + AuthPolicy.BASIC, + new BasicSchemeFactory()); + registry.register( + AuthPolicy.DIGEST, + new DigestSchemeFactory()); + return registry; + } + + + @Override + protected CookieSpecRegistry createCookieSpecRegistry() { + CookieSpecRegistry registry = new CookieSpecRegistry(); + registry.register( + CookiePolicy.BEST_MATCH, + new BestMatchSpecFactory()); + registry.register( + CookiePolicy.BROWSER_COMPATIBILITY, + new BrowserCompatSpecFactory()); + registry.register( + CookiePolicy.NETSCAPE, + new NetscapeDraftSpecFactory()); + registry.register( + CookiePolicy.RFC_2109, + new RFC2109SpecFactory()); + registry.register( + CookiePolicy.RFC_2965, + new RFC2965SpecFactory()); + return registry; + } + + + @Override + protected BasicHttpProcessor createHttpProcessor() { + BasicHttpProcessor httpproc = new BasicHttpProcessor(); + httpproc.addInterceptor(new RequestDefaultHeaders()); + // Required protocol interceptors + httpproc.addInterceptor(new RequestContent()); + httpproc.addInterceptor(new RequestTargetHost()); + // Recommended protocol interceptors + httpproc.addInterceptor(new RequestConnControl()); + httpproc.addInterceptor(new RequestUserAgent()); + httpproc.addInterceptor(new RequestExpectContinue()); + // HTTP state management interceptors + httpproc.addInterceptor(new RequestAddCookies()); + httpproc.addInterceptor(new ResponseProcessCookies()); + // HTTP authentication interceptors + httpproc.addInterceptor(new RequestTargetAuthentication()); + httpproc.addInterceptor(new RequestProxyAuthentication()); + return httpproc; + } + + + @Override + protected HttpRequestRetryHandler createHttpRequestRetryHandler() { + return new DefaultHttpRequestRetryHandler(); + } + + + @Override + protected RedirectHandler createRedirectHandler() { + return new DefaultRedirectHandler(); + } + + + @Override + protected AuthenticationHandler createTargetAuthenticationHandler() { + return new DefaultTargetAuthenticationHandler(); + } + + + @Override + protected AuthenticationHandler createProxyAuthenticationHandler() { + return new DefaultProxyAuthenticationHandler(); + } + + + @Override + protected CookieStore createCookieStore() { + return new BasicCookieStore(); + } + + + @Override + protected CredentialsProvider createCredentialsProvider() { + return new BasicCredentialsProvider(); + } + + + @Override + protected HttpRoutePlanner createHttpRoutePlanner() { + return new DefaultHttpRoutePlanner + (getConnectionManager().getSchemeRegistry()); + } + + + @Override + protected UserTokenHandler createUserTokenHandler() { + return new DefaultUserTokenHandler(); + } + +} // class DefaultHttpClient diff --git a/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java b/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java new file mode 100644 index 0000000..7f66990 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java @@ -0,0 +1,134 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java $ + * $Revision: 652726 $ + * $Date: 2008-05-01 18:16:51 -0700 (Thu, 01 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLHandshakeException; + +import org.apache.http.NoHttpResponseException; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; + +/** + * The default {@link HttpRequestRetryHandler} used by request executors. + * + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler { + + /** the number of times a method will be retried */ + private final int retryCount; + + /** Whether or not methods that have successfully sent their request will be retried */ + private final boolean requestSentRetryEnabled; + + /** + * Default constructor + */ + public DefaultHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled) { + super(); + this.retryCount = retryCount; + this.requestSentRetryEnabled = requestSentRetryEnabled; + } + + /** + * Default constructor + */ + public DefaultHttpRequestRetryHandler() { + this(3, false); + } + /** + * Used <code>retryCount</code> and <code>requestSentRetryEnabled</code> to determine + * if the given method should be retried. + */ + public boolean retryRequest( + final IOException exception, + int executionCount, + final HttpContext context) { + if (exception == null) { + throw new IllegalArgumentException("Exception parameter may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + if (executionCount > this.retryCount) { + // Do not retry if over max retry count + return false; + } + if (exception instanceof NoHttpResponseException) { + // Retry if the server dropped connection on us + return true; + } + if (exception instanceof InterruptedIOException) { + // Timeout + return false; + } + if (exception instanceof UnknownHostException) { + // Unknown host + return false; + } + if (exception instanceof SSLHandshakeException) { + // SSL handshake exception + return false; + } + Boolean b = (Boolean) + context.getAttribute(ExecutionContext.HTTP_REQ_SENT); + boolean sent = (b != null && b.booleanValue()); + if (!sent || this.requestSentRetryEnabled) { + // Retry if the request has not been sent fully or + // if it's OK to retry methods that have been sent + return true; + } + // otherwise do not retry + return false; + } + + /** + * @return <code>true</code> if this handler will retry methods that have + * successfully sent their request, <code>false</code> otherwise + */ + public boolean isRequestSentRetryEnabled() { + return requestSentRetryEnabled; + } + + /** + * @return the maximum number of times a method will be retried + */ + public int getRetryCount() { + return retryCount; + } +} diff --git a/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java b/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java new file mode 100644 index 0000000..c188da8 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java $ + * $Revision: 603615 $ + * $Date: 2007-12-12 06:03:21 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultProxyAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultProxyAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + Header[] headers = response.getHeaders(AUTH.PROXY_AUTH); + return parseChallenges(headers); + } + +} diff --git a/src/org/apache/http/impl/client/DefaultRedirectHandler.java b/src/org/apache/http/impl/client/DefaultRedirectHandler.java new file mode 100644 index 0000000..0811b28 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultRedirectHandler.java @@ -0,0 +1,183 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultRedirectHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.ProtocolException; +import org.apache.http.client.CircularRedirectException; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; + + +/** + * Default implementation of {@link RedirectHandler}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class DefaultRedirectHandler implements RedirectHandler { + + private final Log log = LogFactory.getLog(getClass()); + + private static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; + + public DefaultRedirectHandler() { + super(); + } + + public boolean isRedirectRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int statusCode = response.getStatusLine().getStatusCode(); + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_SEE_OTHER: + case HttpStatus.SC_TEMPORARY_REDIRECT: + return true; + default: + return false; + } //end of switch + } + + public URI getLocationURI( + final HttpResponse response, + final HttpContext context) throws ProtocolException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + //get the location header to find out where to redirect to + Header locationHeader = response.getFirstHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + throw new ProtocolException( + "Received redirect response " + response.getStatusLine() + + " but no location header"); + } + String location = locationHeader.getValue(); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirect requested to location '" + location + "'"); + } + + URI uri; + try { + uri = new URI(location); + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid redirect URI: " + location, ex); + } + + HttpParams params = response.getParams(); + // rfc2616 demands the location value be a complete URI + // Location = "Location" ":" absoluteURI + if (!uri.isAbsolute()) { + if (params.isParameterTrue(ClientPNames.REJECT_RELATIVE_REDIRECT)) { + throw new ProtocolException("Relative redirect location '" + + uri + "' not allowed"); + } + // Adjust location URI + HttpHost target = (HttpHost) context.getAttribute( + ExecutionContext.HTTP_TARGET_HOST); + if (target == null) { + throw new IllegalStateException("Target host not available " + + "in the HTTP context"); + } + + HttpRequest request = (HttpRequest) context.getAttribute( + ExecutionContext.HTTP_REQUEST); + + try { + URI requestURI = new URI(request.getRequestLine().getUri()); + URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, true); + uri = URIUtils.resolve(absoluteRequestURI, uri); + } catch (URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } + + if (params.isParameterFalse(ClientPNames.ALLOW_CIRCULAR_REDIRECTS)) { + + RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute( + REDIRECT_LOCATIONS); + + if (redirectLocations == null) { + redirectLocations = new RedirectLocations(); + context.setAttribute(REDIRECT_LOCATIONS, redirectLocations); + } + + URI redirectURI; + if (uri.getFragment() != null) { + try { + HttpHost target = new HttpHost( + uri.getHost(), + uri.getPort(), + uri.getScheme()); + redirectURI = URIUtils.rewriteURI(uri, target, true); + } catch (URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } else { + redirectURI = uri; + } + + if (redirectLocations.contains(redirectURI)) { + throw new CircularRedirectException("Circular redirect to '" + + redirectURI + "'"); + } else { + redirectLocations.add(redirectURI); + } + } + + return uri; + } + +} diff --git a/src/org/apache/http/impl/client/DefaultRequestDirector.java b/src/org/apache/http/impl/client/DefaultRequestDirector.java new file mode 100644 index 0000000..511f8a0 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultRequestDirector.java @@ -0,0 +1,1088 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultRequestDirector.java $ + * $Revision: 676023 $ + * $Date: 2008-07-11 09:40:56 -0700 (Fri, 11 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolException; +import org.apache.http.ProtocolVersion; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.RequestDirector; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.NonRepeatableRequestException; +import org.apache.http.client.RedirectException; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.methods.AbortableHttpRequest; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.HttpClientParams; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.conn.BasicManagedEntity; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.params.ConnManagerParams; +import org.apache.http.conn.routing.BasicRouteDirector; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRouteDirector; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.message.BasicHttpRequest; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpProcessor; +import org.apache.http.protocol.HttpRequestExecutor; + +/** + * Default implementation of {@link RequestDirector}. + * <br/> + * This class replaces the <code>HttpMethodDirector</code> in HttpClient 3. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 676023 $ + * + * @since 4.0 + */ +public class DefaultRequestDirector implements RequestDirector { + + private final Log log = LogFactory.getLog(getClass()); + + /** The connection manager. */ + protected final ClientConnectionManager connManager; + + /** The route planner. */ + protected final HttpRoutePlanner routePlanner; + + /** The connection re-use strategy. */ + protected final ConnectionReuseStrategy reuseStrategy; + + /** The keep-alive duration strategy. */ + protected final ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The request executor. */ + protected final HttpRequestExecutor requestExec; + + /** The HTTP protocol processor. */ + protected final HttpProcessor httpProcessor; + + /** The request retry handler. */ + protected final HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + protected final RedirectHandler redirectHandler; + + /** The target authentication handler. */ + private final AuthenticationHandler targetAuthHandler; + + /** The proxy authentication handler. */ + private final AuthenticationHandler proxyAuthHandler; + + /** The user token handler. */ + private final UserTokenHandler userTokenHandler; + + /** The HTTP parameters. */ + protected final HttpParams params; + + /** The currently allocated connection. */ + protected ManagedClientConnection managedConn; + + private int redirectCount; + + private int maxRedirects; + + private final AuthState targetAuthState; + + private final AuthState proxyAuthState; + + public DefaultRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + + if (requestExec == null) { + throw new IllegalArgumentException + ("Request executor may not be null."); + } + if (conman == null) { + throw new IllegalArgumentException + ("Client connection manager may not be null."); + } + if (reustrat == null) { + throw new IllegalArgumentException + ("Connection reuse strategy may not be null."); + } + if (kastrat == null) { + throw new IllegalArgumentException + ("Connection keep alive strategy may not be null."); + } + if (rouplan == null) { + throw new IllegalArgumentException + ("Route planner may not be null."); + } + if (httpProcessor == null) { + throw new IllegalArgumentException + ("HTTP protocol processor may not be null."); + } + if (retryHandler == null) { + throw new IllegalArgumentException + ("HTTP request retry handler may not be null."); + } + if (redirectHandler == null) { + throw new IllegalArgumentException + ("Redirect handler may not be null."); + } + if (targetAuthHandler == null) { + throw new IllegalArgumentException + ("Target authentication handler may not be null."); + } + if (proxyAuthHandler == null) { + throw new IllegalArgumentException + ("Proxy authentication handler may not be null."); + } + if (userTokenHandler == null) { + throw new IllegalArgumentException + ("User token handler may not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("HTTP parameters may not be null"); + } + this.requestExec = requestExec; + this.connManager = conman; + this.reuseStrategy = reustrat; + this.keepAliveStrategy = kastrat; + this.routePlanner = rouplan; + this.httpProcessor = httpProcessor; + this.retryHandler = retryHandler; + this.redirectHandler = redirectHandler; + this.targetAuthHandler = targetAuthHandler; + this.proxyAuthHandler = proxyAuthHandler; + this.userTokenHandler = userTokenHandler; + this.params = params; + + this.managedConn = null; + + this.redirectCount = 0; + this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100); + this.targetAuthState = new AuthState(); + this.proxyAuthState = new AuthState(); + } // constructor + + + private RequestWrapper wrapRequest( + final HttpRequest request) throws ProtocolException { + if (request instanceof HttpEntityEnclosingRequest) { + return new EntityEnclosingRequestWrapper( + (HttpEntityEnclosingRequest) request); + } else { + return new RequestWrapper( + request); + } + } + + + protected void rewriteRequestURI( + final RequestWrapper request, + final HttpRoute route) throws ProtocolException { + try { + + URI uri = request.getURI(); + if (route.getProxyHost() != null && !route.isTunnelled()) { + // Make sure the request URI is absolute + if (!uri.isAbsolute()) { + HttpHost target = route.getTargetHost(); + uri = URIUtils.rewriteURI(uri, target); + request.setURI(uri); + } + } else { + // Make sure the request URI is relative + if (uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, null); + request.setURI(uri); + } + } + + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid URI: " + + request.getRequestLine().getUri(), ex); + } + } + + + // non-javadoc, see interface ClientRequestDirector + public HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) + throws HttpException, IOException { + + HttpRequest orig = request; + RequestWrapper origWrapper = wrapRequest(orig); + origWrapper.setParams(params); + HttpRoute origRoute = determineRoute(target, origWrapper, context); + + RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute); + + long timeout = ConnManagerParams.getTimeout(params); + + int execCount = 0; + + boolean reuse = false; + HttpResponse response = null; + boolean done = false; + try { + while (!done) { + // In this loop, the RoutedRequest may be replaced by a + // followup request and route. The request and route passed + // in the method arguments will be replaced. The original + // request is still available in 'orig'. + + RequestWrapper wrapper = roureq.getRequest(); + HttpRoute route = roureq.getRoute(); + + // See if we have a user token bound to the execution context + Object userToken = context.getAttribute(ClientContext.USER_TOKEN); + + // Allocate connection if needed + if (managedConn == null) { + ClientConnectionRequest connRequest = connManager.requestConnection( + route, userToken); + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setConnectionRequest(connRequest); + } + + try { + managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS); + } catch(InterruptedException interrupted) { + InterruptedIOException iox = new InterruptedIOException(); + iox.initCause(interrupted); + throw iox; + } + + if (HttpConnectionParams.isStaleCheckingEnabled(params)) { + // validate connection + this.log.debug("Stale connection check"); + if (managedConn.isStale()) { + this.log.debug("Stale connection detected"); + managedConn.close(); + } + } + } + + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn); + } + + // Reopen connection if needed + if (!managedConn.isOpen()) { + managedConn.open(route, context, params); + } + + try { + establishRoute(route, context); + } catch (TunnelRefusedException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage()); + } + response = ex.getResponse(); + break; + } + + // Reset headers on the request wrapper + wrapper.resetHeaders(); + + // Re-write request URI if needed + rewriteRequestURI(wrapper, route); + + // Use virtual host if set + target = (HttpHost) wrapper.getParams().getParameter( + ClientPNames.VIRTUAL_HOST); + + if (target == null) { + target = route.getTargetHost(); + } + + HttpHost proxy = route.getProxyHost(); + + // Populate the execution context + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, + target); + context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, + proxy); + context.setAttribute(ExecutionContext.HTTP_CONNECTION, + managedConn); + context.setAttribute(ClientContext.TARGET_AUTH_STATE, + targetAuthState); + context.setAttribute(ClientContext.PROXY_AUTH_STATE, + proxyAuthState); + + // Run request protocol interceptors + requestExec.preProcess(wrapper, httpProcessor, context); + + context.setAttribute(ExecutionContext.HTTP_REQUEST, + wrapper); + + boolean retrying = true; + while (retrying) { + // Increment total exec count (with redirects) + execCount++; + // Increment exec count for this particular request + wrapper.incrementExecCount(); + if (wrapper.getExecCount() > 1 && !wrapper.isRepeatable()) { + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity"); + } + + try { + if (this.log.isDebugEnabled()) { + this.log.debug("Attempt " + execCount + " to execute request"); + } + response = requestExec.execute(wrapper, managedConn, context); + retrying = false; + + } catch (IOException ex) { + this.log.debug("Closing the connection."); + managedConn.close(); + if (retryHandler.retryRequest(ex, execCount, context)) { + if (this.log.isInfoEnabled()) { + this.log.info("I/O exception ("+ ex.getClass().getName() + + ") caught when processing request: " + + ex.getMessage()); + } + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + this.log.info("Retrying request"); + } else { + throw ex; + } + + // If we have a direct route to the target host + // just re-open connection and re-try the request + if (route.getHopCount() == 1) { + this.log.debug("Reopening the direct connection."); + managedConn.open(route, context, params); + } else { + // otherwise give up + retrying = false; + } + + } + + } + + // Run response protocol interceptors + response.setParams(params); + requestExec.postProcess(response, httpProcessor, context); + + + // The connection is in or can be brought to a re-usable state. + reuse = reuseStrategy.keepAlive(response, context); + if(reuse) { + // Set the idle duration of this connection + long duration = keepAliveStrategy.getKeepAliveDuration(response, context); + managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS); + } + + RoutedRequest followup = handleResponse(roureq, response, context); + if (followup == null) { + done = true; + } else { + if (reuse) { + this.log.debug("Connection kept alive"); + // Make sure the response body is fully consumed, if present + HttpEntity entity = response.getEntity(); + if (entity != null) { + entity.consumeContent(); + } + // entity consumed above is not an auto-release entity, + // need to mark the connection re-usable explicitly + managedConn.markReusable(); + } else { + managedConn.close(); + } + // check if we can use the same connection for the followup + if (!followup.getRoute().equals(roureq.getRoute())) { + releaseConnection(); + } + roureq = followup; + } + + userToken = this.userTokenHandler.getUserToken(context); + context.setAttribute(ClientContext.USER_TOKEN, userToken); + if (managedConn != null) { + managedConn.setState(userToken); + } + } // while not done + + + // check for entity, release connection if possible + if ((response == null) || (response.getEntity() == null) || + !response.getEntity().isStreaming()) { + // connection not needed and (assumed to be) in re-usable state + if (reuse) + managedConn.markReusable(); + releaseConnection(); + } else { + // install an auto-release entity + HttpEntity entity = response.getEntity(); + entity = new BasicManagedEntity(entity, managedConn, reuse); + response.setEntity(entity); + } + + return response; + + } catch (HttpException ex) { + abortConnection(); + throw ex; + } catch (IOException ex) { + abortConnection(); + throw ex; + } catch (RuntimeException ex) { + abortConnection(); + throw ex; + } + } // execute + + /** + * Returns the connection back to the connection manager + * and prepares for retrieving a new connection during + * the next request. + */ + protected void releaseConnection() { + // Release the connection through the ManagedConnection instead of the + // ConnectionManager directly. This lets the connection control how + // it is released. + try { + managedConn.releaseConnection(); + } catch(IOException ignored) { + this.log.debug("IOException releasing connection", ignored); + } + managedConn = null; + } + + /** + * Determines the route for a request. + * Called by {@link #execute} + * to determine the route for either the original or a followup request. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context to use for the execution, + * never <code>null</code> + * + * @return the route the request should take + * + * @throws HttpException in case of a problem + */ + protected HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (target == null) { + target = (HttpHost) request.getParams().getParameter( + ClientPNames.DEFAULT_HOST); + } + if (target == null) { + throw new IllegalStateException + ("Target host must not be null, or set in parameters."); + } + + return this.routePlanner.determineRoute(target, request, context); + } + + + /** + * Establishes the target route. + * + * @param route the route to establish + * @param context the context for the request execution + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected void establishRoute(HttpRoute route, HttpContext context) + throws HttpException, IOException { + + //@@@ how to handle CONNECT requests for tunnelling? + //@@@ refuse to send external CONNECT via director? special handling? + + //@@@ should the request parameters already be used below? + //@@@ probably yes, but they're not linked yet + //@@@ will linking above cause problems with linking in reqExec? + //@@@ probably not, because the parent is replaced + //@@@ just make sure we don't link parameters to themselves + + HttpRouteDirector rowdy = new BasicRouteDirector(); + int step; + do { + HttpRoute fact = managedConn.getRoute(); + step = rowdy.nextStep(route, fact); + + switch (step) { + + case HttpRouteDirector.CONNECT_TARGET: + case HttpRouteDirector.CONNECT_PROXY: + managedConn.open(route, context, this.params); + break; + + case HttpRouteDirector.TUNNEL_TARGET: { + boolean secure = createTunnelToTarget(route, context); + this.log.debug("Tunnel to target created."); + managedConn.tunnelTarget(secure, this.params); + } break; + + case HttpRouteDirector.TUNNEL_PROXY: { + // The most simple example for this case is a proxy chain + // of two proxies, where P1 must be tunnelled to P2. + // route: Source -> P1 -> P2 -> Target (3 hops) + // fact: Source -> P1 -> Target (2 hops) + final int hop = fact.getHopCount()-1; // the hop to establish + boolean secure = createTunnelToProxy(route, hop, context); + this.log.debug("Tunnel to proxy created."); + managedConn.tunnelProxy(route.getHopTarget(hop), + secure, this.params); + } break; + + + case HttpRouteDirector.LAYER_PROTOCOL: + managedConn.layerProtocol(context, this.params); + break; + + case HttpRouteDirector.UNREACHABLE: + throw new IllegalStateException + ("Unable to establish route." + + "\nplanned = " + route + + "\ncurrent = " + fact); + + case HttpRouteDirector.COMPLETE: + // do nothing + break; + + default: + throw new IllegalStateException + ("Unknown step indicator "+step+" from RouteDirector."); + } // switch + + } while (step > HttpRouteDirector.COMPLETE); + + } // establishConnection + + + /** + * Creates a tunnel to the target server. + * The connection must be established to the (last) proxy. + * A CONNECT request for tunnelling through the proxy will + * be created and sent, the response received and checked. + * This method does <i>not</i> update the connection with + * information about the tunnel, that is left to the caller. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return <code>true</code> if the tunnelled route is secure, + * <code>false</code> otherwise. + * The implementation here always returns <code>false</code>, + * but derived classes may override. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToTarget(HttpRoute route, + HttpContext context) + throws HttpException, IOException { + + HttpHost proxy = route.getProxyHost(); + HttpHost target = route.getTargetHost(); + HttpResponse response = null; + + boolean done = false; + while (!done) { + + done = true; + + if (!this.managedConn.isOpen()) { + this.managedConn.open(route, context, this.params); + } + + HttpRequest connect = createConnectRequest(route, context); + + String agent = HttpProtocolParams.getUserAgent(params); + if (agent != null) { + connect.addHeader(HTTP.USER_AGENT, agent); + } + connect.addHeader(HTTP.TARGET_HOST, target.toHostString()); + + AuthScheme authScheme = this.proxyAuthState.getAuthScheme(); + AuthScope authScope = this.proxyAuthState.getAuthScope(); + Credentials creds = this.proxyAuthState.getCredentials(); + if (creds != null) { + if (authScope != null || !authScheme.isConnectionBased()) { + try { + connect.addHeader(authScheme.authenticate(creds, connect)); + } catch (AuthenticationException ex) { + if (this.log.isErrorEnabled()) { + this.log.error("Proxy authentication error: " + ex.getMessage()); + } + } + } + } + + response = requestExec.execute(connect, this.managedConn, context); + + int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + + CredentialsProvider credsProvider = (CredentialsProvider) + context.getAttribute(ClientContext.CREDS_PROVIDER); + + if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { + if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { + + this.log.debug("Proxy requested authentication"); + Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( + response, context); + try { + processChallenges( + challenges, this.proxyAuthState, this.proxyAuthHandler, + response, context); + } catch (AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication error: " + ex.getMessage()); + break; + } + } + updateAuthState(this.proxyAuthState, proxy, credsProvider); + + if (this.proxyAuthState.getCredentials() != null) { + done = false; + + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + this.log.debug("Connection kept alive"); + // Consume response content + HttpEntity entity = response.getEntity(); + if (entity != null) { + entity.consumeContent(); + } + } else { + this.managedConn.close(); + } + + } + + } else { + // Reset proxy auth scope + this.proxyAuthState.setAuthScope(null); + } + } + } + + int status = response.getStatusLine().getStatusCode(); + + if (status > 299) { + + // Buffer response content + HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + this.managedConn.close(); + throw new TunnelRefusedException("CONNECT refused by proxy: " + + response.getStatusLine(), response); + } + + this.managedConn.markReusable(); + + // How to decide on security of the tunnelled connection? + // The socket factory knows only about the segment to the proxy. + // Even if that is secure, the hop to the target may be insecure. + // Leave it to derived classes, consider insecure by default here. + return false; + + } // createTunnelToTarget + + + + /** + * Creates a tunnel to an intermediate proxy. + * This method is <i>not</i> implemented in this class. + * It just throws an exception here. + * + * @param route the route to establish + * @param hop the hop in the route to establish now. + * <code>route.getHopTarget(hop)</code> + * will return the proxy to tunnel to. + * @param context the context for request execution + * + * @return <code>true</code> if the partially tunnelled connection + * is secure, <code>false</code> otherwise. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToProxy(HttpRoute route, int hop, + HttpContext context) + throws HttpException, IOException { + + // Have a look at createTunnelToTarget and replicate the parts + // you need in a custom derived class. If your proxies don't require + // authentication, it is not too hard. But for the stock version of + // HttpClient, we cannot make such simplifying assumptions and would + // have to include proxy authentication code. The HttpComponents team + // is currently not in a position to support rarely used code of this + // complexity. Feel free to submit patches that refactor the code in + // createTunnelToTarget to facilitate re-use for proxy tunnelling. + + throw new UnsupportedOperationException + ("Proxy chains are not supported."); + } + + + + /** + * Creates the CONNECT request for tunnelling. + * Called by {@link #createTunnelToTarget createTunnelToTarget}. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return the CONNECT request for tunnelling + */ + protected HttpRequest createConnectRequest(HttpRoute route, + HttpContext context) { + // see RFC 2817, section 5.2 and + // INTERNET-DRAFT: Tunneling TCP based protocols through + // Web proxy servers + + HttpHost target = route.getTargetHost(); + + String host = target.getHostName(); + int port = target.getPort(); + if (port < 0) { + Scheme scheme = connManager.getSchemeRegistry(). + getScheme(target.getSchemeName()); + port = scheme.getDefaultPort(); + } + + StringBuilder buffer = new StringBuilder(host.length() + 6); + buffer.append(host); + buffer.append(':'); + buffer.append(Integer.toString(port)); + + String authority = buffer.toString(); + ProtocolVersion ver = HttpProtocolParams.getVersion(params); + HttpRequest req = new BasicHttpRequest + ("CONNECT", authority, ver); + + return req; + } + + + /** + * Analyzes a response to check need for a followup. + * + * @param roureq the request and route. + * @param response the response to analayze + * @param context the context used for the current request execution + * + * @return the followup request and route if there is a followup, or + * <code>null</code> if the response should be returned as is + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected RoutedRequest handleResponse(RoutedRequest roureq, + HttpResponse response, + HttpContext context) + throws HttpException, IOException { + + HttpRoute route = roureq.getRoute(); + HttpHost proxy = route.getProxyHost(); + RequestWrapper request = roureq.getRequest(); + + HttpParams params = request.getParams(); + if (HttpClientParams.isRedirecting(params) && + this.redirectHandler.isRedirectRequested(response, context)) { + + if (redirectCount >= maxRedirects) { + throw new RedirectException("Maximum redirects (" + + maxRedirects + ") exceeded"); + } + redirectCount++; + + URI uri = this.redirectHandler.getLocationURI(response, context); + + HttpHost newTarget = new HttpHost( + uri.getHost(), + uri.getPort(), + uri.getScheme()); + + HttpGet redirect = new HttpGet(uri); + + HttpRequest orig = request.getOriginal(); + redirect.setHeaders(orig.getAllHeaders()); + + RequestWrapper wrapper = new RequestWrapper(redirect); + wrapper.setParams(params); + + HttpRoute newRoute = determineRoute(newTarget, wrapper, context); + RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute); + + if (this.log.isDebugEnabled()) { + this.log.debug("Redirecting to '" + uri + "' via " + newRoute); + } + + return newRequest; + } + + CredentialsProvider credsProvider = (CredentialsProvider) + context.getAttribute(ClientContext.CREDS_PROVIDER); + + if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { + + if (this.targetAuthHandler.isAuthenticationRequested(response, context)) { + + HttpHost target = (HttpHost) + context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); + if (target == null) { + target = route.getTargetHost(); + } + + this.log.debug("Target requested authentication"); + Map<String, Header> challenges = this.targetAuthHandler.getChallenges( + response, context); + try { + processChallenges(challenges, + this.targetAuthState, this.targetAuthHandler, + response, context); + } catch (AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication error: " + ex.getMessage()); + return null; + } + } + updateAuthState(this.targetAuthState, target, credsProvider); + + if (this.targetAuthState.getCredentials() != null) { + // Re-try the same request via the same route + return roureq; + } else { + return null; + } + } else { + // Reset target auth scope + this.targetAuthState.setAuthScope(null); + } + + if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { + + this.log.debug("Proxy requested authentication"); + Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( + response, context); + try { + processChallenges(challenges, + this.proxyAuthState, this.proxyAuthHandler, + response, context); + } catch (AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication error: " + ex.getMessage()); + return null; + } + } + updateAuthState(this.proxyAuthState, proxy, credsProvider); + + if (this.proxyAuthState.getCredentials() != null) { + // Re-try the same request via the same route + return roureq; + } else { + return null; + } + } else { + // Reset proxy auth scope + this.proxyAuthState.setAuthScope(null); + } + } + return null; + } // handleResponse + + + /** + * Shuts down the connection. + * This method is called from a <code>catch</code> block in + * {@link #execute execute} during exception handling. + */ + private void abortConnection() { + ManagedClientConnection mcc = managedConn; + if (mcc != null) { + // we got here as the result of an exception + // no response will be returned, release the connection + managedConn = null; + try { + mcc.abortConnection(); + } catch (IOException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + } + // ensure the connection manager properly releases this connection + try { + mcc.releaseConnection(); + } catch(IOException ignored) { + this.log.debug("Error releasing connection", ignored); + } + } + } // abortConnection + + + private void processChallenges( + final Map<String, Header> challenges, + final AuthState authState, + final AuthenticationHandler authHandler, + final HttpResponse response, + final HttpContext context) + throws MalformedChallengeException, AuthenticationException { + + AuthScheme authScheme = authState.getAuthScheme(); + if (authScheme == null) { + // Authentication not attempted before + authScheme = authHandler.selectScheme(challenges, response, context); + authState.setAuthScheme(authScheme); + } + String id = authScheme.getSchemeName(); + + Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + if (challenge == null) { + throw new AuthenticationException(id + + " authorization challenge expected, but not found"); + } + authScheme.processChallenge(challenge); + this.log.debug("Authorization challenge processed"); + } + + + private void updateAuthState( + final AuthState authState, + final HttpHost host, + final CredentialsProvider credsProvider) { + + if (!authState.isValid()) { + return; + } + + String hostname = host.getHostName(); + int port = host.getPort(); + if (port < 0) { + Scheme scheme = connManager.getSchemeRegistry().getScheme(host); + port = scheme.getDefaultPort(); + } + + AuthScheme authScheme = authState.getAuthScheme(); + AuthScope authScope = new AuthScope( + hostname, + port, + authScheme.getRealm(), + authScheme.getSchemeName()); + + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication scope: " + authScope); + } + Credentials creds = authState.getCredentials(); + if (creds == null) { + creds = credsProvider.getCredentials(authScope); + if (this.log.isDebugEnabled()) { + if (creds != null) { + this.log.debug("Found credentials"); + } else { + this.log.debug("Credentials not found"); + } + } + } else { + if (authScheme.isComplete()) { + this.log.debug("Authentication failed"); + creds = null; + } + } + authState.setAuthScope(authScope); + authState.setCredentials(creds); + } + +} // class DefaultClientRequestDirector diff --git a/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java b/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java new file mode 100644 index 0000000..5794549 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java $ + * $Revision: 603615 $ + * $Date: 2007-12-12 06:03:21 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultTargetAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultTargetAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_UNAUTHORIZED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + Header[] headers = response.getHeaders(AUTH.WWW_AUTH); + return parseChallenges(headers); + } + +} diff --git a/src/org/apache/http/impl/client/DefaultUserTokenHandler.java b/src/org/apache/http/impl/client/DefaultUserTokenHandler.java new file mode 100644 index 0000000..c8a409f --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultUserTokenHandler.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java $ + * $Revision: 659971 $ + * $Date: 2008-05-25 05:01:22 -0700 (Sun, 25 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.security.Principal; + +import javax.net.ssl.SSLSession; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.protocol.HttpContext; + +public class DefaultUserTokenHandler implements UserTokenHandler { + + public Object getUserToken(final HttpContext context) { + + Principal userPrincipal = null; + + AuthState targetAuthState = (AuthState) context.getAttribute( + ClientContext.TARGET_AUTH_STATE); + if (targetAuthState != null) { + userPrincipal = getAuthPrincipal(targetAuthState); + if (userPrincipal == null) { + AuthState proxyAuthState = (AuthState) context.getAttribute( + ClientContext.PROXY_AUTH_STATE); + userPrincipal = getAuthPrincipal(proxyAuthState); + } + } + + if (userPrincipal == null) { + ManagedClientConnection conn = (ManagedClientConnection) context.getAttribute( + ExecutionContext.HTTP_CONNECTION); + if (conn.isOpen()) { + SSLSession sslsession = conn.getSSLSession(); + if (sslsession != null) { + userPrincipal = sslsession.getLocalPrincipal(); + } + } + } + + return userPrincipal; + } + + private static Principal getAuthPrincipal(final AuthState authState) { + AuthScheme scheme = authState.getAuthScheme(); + if (scheme != null && scheme.isComplete() && scheme.isConnectionBased()) { + Credentials creds = authState.getCredentials(); + if (creds != null) { + return creds.getUserPrincipal(); + } + } + return null; + } + +} diff --git a/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java b/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java new file mode 100644 index 0000000..05098cf --- /dev/null +++ b/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.ProtocolException; +import org.apache.http.protocol.HTTP; + +/** + * A wrapper class for {@link HttpEntityEnclosingRequest}s that can + * be used to change properties of the current request without + * modifying the original object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public class EntityEnclosingRequestWrapper extends RequestWrapper + implements HttpEntityEnclosingRequest { + + private HttpEntity entity; + + public EntityEnclosingRequestWrapper(final HttpEntityEnclosingRequest request) + throws ProtocolException { + super(request); + this.entity = request.getEntity(); + } + + public HttpEntity getEntity() { + return this.entity; + } + + public void setEntity(final HttpEntity entity) { + this.entity = entity; + } + + public boolean expectContinue() { + Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); + return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); + } + + @Override + public boolean isRepeatable() { + return this.entity == null || this.entity.isRepeatable(); + } + +} diff --git a/src/org/apache/http/impl/client/RedirectLocations.java b/src/org/apache/http/impl/client/RedirectLocations.java new file mode 100644 index 0000000..d5c47e7 --- /dev/null +++ b/src/org/apache/http/impl/client/RedirectLocations.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RedirectLocations.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.net.URI; +import java.util.HashSet; +import java.util.Set; + +/** + * A collection of URIs that were used as redirects. + */ +public class RedirectLocations { + + private final Set<URI> uris; + + public RedirectLocations() { + super(); + this.uris = new HashSet<URI>(); + } + + /** + * Returns true if this collection contains the given URI. + */ + public boolean contains(final URI uri) { + return this.uris.contains(uri); + } + + /** + * Adds a new URI to the list of redirects. + */ + public void add(final URI uri) { + this.uris.add(uri); + } + + /** + * Removes a URI from the list of redirects. + */ + public boolean remove(final URI uri) { + return this.uris.remove(uri); + } + +} diff --git a/src/org/apache/http/impl/client/RequestWrapper.java b/src/org/apache/http/impl/client/RequestWrapper.java new file mode 100644 index 0000000..04a641d --- /dev/null +++ b/src/org/apache/http/impl/client/RequestWrapper.java @@ -0,0 +1,170 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RequestWrapper.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.http.HttpRequest; +import org.apache.http.ProtocolException; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.AbstractHttpMessage; +import org.apache.http.message.BasicRequestLine; +import org.apache.http.params.HttpProtocolParams; + +/** + * A wrapper class for {@link HttpRequest}s that can be used to change + * properties of the current request without modifying the original + * object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public class RequestWrapper extends AbstractHttpMessage implements HttpUriRequest { + + private final HttpRequest original; + + private URI uri; + private String method; + private ProtocolVersion version; + private int execCount; + + public RequestWrapper(final HttpRequest request) throws ProtocolException { + super(); + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + this.original = request; + setParams(request.getParams()); + // Make a copy of the original URI + if (request instanceof HttpUriRequest) { + this.uri = ((HttpUriRequest) request).getURI(); + this.method = ((HttpUriRequest) request).getMethod(); + this.version = null; + } else { + RequestLine requestLine = request.getRequestLine(); + try { + this.uri = new URI(requestLine.getUri()); + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid request URI: " + + requestLine.getUri(), ex); + } + this.method = requestLine.getMethod(); + this.version = request.getProtocolVersion(); + } + this.execCount = 0; + } + + public void resetHeaders() { + // Make a copy of original headers + this.headergroup.clear(); + setHeaders(this.original.getAllHeaders()); + } + + public String getMethod() { + return this.method; + } + + public void setMethod(final String method) { + if (method == null) { + throw new IllegalArgumentException("Method name may not be null"); + } + this.method = method; + } + + public ProtocolVersion getProtocolVersion() { + if (this.version != null) { + return this.version; + } else { + return HttpProtocolParams.getVersion(getParams()); + } + } + + public void setProtocolVersion(final ProtocolVersion version) { + this.version = version; + } + + + public URI getURI() { + return this.uri; + } + + public void setURI(final URI uri) { + this.uri = uri; + } + + public RequestLine getRequestLine() { + String method = getMethod(); + ProtocolVersion ver = getProtocolVersion(); + String uritext = null; + if (uri != null) { + uritext = uri.toASCIIString(); + } + if (uritext == null || uritext.length() == 0) { + uritext = "/"; + } + return new BasicRequestLine(method, uritext, ver); + } + + public void abort() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean isAborted() { + return false; + } + + public HttpRequest getOriginal() { + return this.original; + } + + public boolean isRepeatable() { + return true; + } + + public int getExecCount() { + return this.execCount; + } + + public void incrementExecCount() { + this.execCount++; + } + +} diff --git a/src/org/apache/http/impl/client/RoutedRequest.java b/src/org/apache/http/impl/client/RoutedRequest.java new file mode 100644 index 0000000..954ebe5 --- /dev/null +++ b/src/org/apache/http/impl/client/RoutedRequest.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RoutedRequest.java $ + * $Revision: 645846 $ + * $Date: 2008-04-08 03:53:39 -0700 (Tue, 08 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.conn.routing.HttpRoute; + + +/** + * A request with the route along which it should be sent. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 645846 $ + * + * @since 4.0 + */ +public class RoutedRequest { + + protected final RequestWrapper request; + protected final HttpRoute route; + + /** + * Creates a new routed request. + * + * @param req the request + * @param route the route + */ + public RoutedRequest(final RequestWrapper req, final HttpRoute route) { + super(); + this.request = req; + this.route = route; + } + + public final RequestWrapper getRequest() { + return request; + } + + public final HttpRoute getRoute() { + return route; + } + +} // interface RoutedRequest diff --git a/src/org/apache/http/impl/client/TunnelRefusedException.java b/src/org/apache/http/impl/client/TunnelRefusedException.java new file mode 100644 index 0000000..601626f --- /dev/null +++ b/src/org/apache/http/impl/client/TunnelRefusedException.java @@ -0,0 +1,52 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/TunnelRefusedException.java $ + * $Revision: 537650 $ + * $Date: 2007-05-13 12:58:22 -0700 (Sun, 13 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; + +public class TunnelRefusedException extends HttpException { + + private static final long serialVersionUID = -8646722842745617323L; + + private final HttpResponse response; + + public TunnelRefusedException(final String message, final HttpResponse response) { + super(message); + this.response = response; + } + + public HttpResponse getResponse() { + return this.response; + } + +} diff --git a/src/org/apache/http/impl/client/package.html b/src/org/apache/http/impl/client/package.html new file mode 100644 index 0000000..e301283 --- /dev/null +++ b/src/org/apache/http/impl/client/package.html @@ -0,0 +1,4 @@ +<body> + +</body> + diff --git a/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java b/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java new file mode 100644 index 0000000..5cbe010 --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java @@ -0,0 +1,399 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractClientConnAdapter.java $ + * $Revision: 672969 $ + * $Date: 2008-06-30 18:09:50 -0700 (Mon, 30 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSession; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.ClientConnectionManager; + + +/** + * Abstract adapter from {@link OperatedClientConnection operated} to + * {@link ManagedClientConnection managed} client connections. + * Read and write methods are delegated to the wrapped connection. + * Operations affecting the connection state have to be implemented + * by derived classes. Operations for querying the connection state + * are delegated to the wrapped connection if there is one, or + * return a default value if there is none. + * <br/> + * This adapter tracks the checkpoints for reusable communication states, + * as indicated by {@link #markReusable markReusable} and queried by + * {@link #isMarkedReusable isMarkedReusable}. + * All send and receive operations will automatically clear the mark. + * <br/> + * Connection release calls are delegated to the connection manager, + * if there is one. {@link #abortConnection abortConnection} will + * clear the reusability mark first. The connection manager is + * expected to tolerate multiple calls to the release method. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672969 $ $Date: 2008-06-30 18:09:50 -0700 (Mon, 30 Jun 2008) $ + * + * @since 4.0 + */ +public abstract class AbstractClientConnAdapter + implements ManagedClientConnection { + + /** Thread that requested this connection. */ + private final Thread executionThread; + + /** + * The connection manager, if any. + * This attribute MUST NOT be final, so the adapter can be detached + * from the connection manager without keeping a hard reference there. + */ + private volatile ClientConnectionManager connManager; + + /** The wrapped connection. */ + private volatile OperatedClientConnection wrappedConnection; + + /** The reusability marker. */ + private volatile boolean markedReusable; + + /** True if the connection has been aborted. */ + private volatile boolean aborted; + + /** The duration this is valid for while idle (in ms). */ + private volatile long duration; + + /** + * Creates a new connection adapter. + * The adapter is initially <i>not</i> + * {@link #isMarkedReusable marked} as reusable. + * + * @param mgr the connection manager, or <code>null</code> + * @param conn the connection to wrap, or <code>null</code> + */ + protected AbstractClientConnAdapter(ClientConnectionManager mgr, + OperatedClientConnection conn) { + super(); + executionThread = Thread.currentThread(); + connManager = mgr; + wrappedConnection = conn; + markedReusable = false; + aborted = false; + duration = Long.MAX_VALUE; + } // <constructor> + + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + protected void detach() { + wrappedConnection = null; + connManager = null; // base class attribute + duration = Long.MAX_VALUE; + } + + protected OperatedClientConnection getWrappedConnection() { + return wrappedConnection; + } + + protected ClientConnectionManager getManager() { + return connManager; + } + + /** + * Asserts that the connection has not been aborted. + * + * @throws InterruptedIOException if the connection has been aborted + */ + protected final void assertNotAborted() throws InterruptedIOException { + if (aborted) { + throw new InterruptedIOException("Connection has been shut down."); + } + } + + /** + * Asserts that there is a wrapped connection to delegate to. + * + * @throws IllegalStateException if there is no wrapped connection + * or connection has been aborted + */ + protected final void assertValid( + final OperatedClientConnection wrappedConn) { + if (wrappedConn == null) { + throw new IllegalStateException("No wrapped connection."); + } + } + + // non-javadoc, see interface HttpConnection + public boolean isOpen() { + OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) + return false; + + return conn.isOpen(); + } + + + // non-javadoc, see interface HttpConnection + public boolean isStale() { + if (aborted) + return true; + OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) + return true; + + return conn.isStale(); + } + + + // non-javadoc, see interface HttpConnection + public void setSocketTimeout(int timeout) { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + conn.setSocketTimeout(timeout); + } + + + // non-javadoc, see interface HttpConnection + public int getSocketTimeout() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getSocketTimeout(); + } + + + // non-javadoc, see interface HttpConnection + public HttpConnectionMetrics getMetrics() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getMetrics(); + } + + + // non-javadoc, see interface HttpClientConnection + public void flush() + throws IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + conn.flush(); + } + + + // non-javadoc, see interface HttpClientConnection + public boolean isResponseAvailable(int timeout) + throws IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + return conn.isResponseAvailable(timeout); + } + + + // non-javadoc, see interface HttpClientConnection + public void receiveResponseEntity(HttpResponse response) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.receiveResponseEntity(response); + } + + + // non-javadoc, see interface HttpClientConnection + public HttpResponse receiveResponseHeader() + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + return conn.receiveResponseHeader(); + } + + + // non-javadoc, see interface HttpClientConnection + public void sendRequestEntity(HttpEntityEnclosingRequest request) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.sendRequestEntity(request); + } + + + // non-javadoc, see interface HttpClientConnection + public void sendRequestHeader(HttpRequest request) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.sendRequestHeader(request); + } + + + // non-javadoc, see interface HttpInetConnection + public InetAddress getLocalAddress() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalAddress(); + } + + // non-javadoc, see interface HttpInetConnection + public int getLocalPort() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalPort(); + } + + + // non-javadoc, see interface HttpInetConnection + public InetAddress getRemoteAddress() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemoteAddress(); + } + + // non-javadoc, see interface HttpInetConnection + public int getRemotePort() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemotePort(); + } + + // non-javadoc, see interface ManagedClientConnection + public boolean isSecure() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.isSecure(); + } + + // non-javadoc, see interface ManagedClientConnection + public SSLSession getSSLSession() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (!isOpen()) + return null; + + SSLSession result = null; + Socket sock = conn.getSocket(); + if (sock instanceof SSLSocket) { + result = ((SSLSocket)sock).getSession(); + } + return result; + } + + // non-javadoc, see interface ManagedClientConnection + public void markReusable() { + markedReusable = true; + } + + // non-javadoc, see interface ManagedClientConnection + public void unmarkReusable() { + markedReusable = false; + } + + // non-javadoc, see interface ManagedClientConnection + public boolean isMarkedReusable() { + return markedReusable; + } + + public void setIdleDuration(long duration, TimeUnit unit) { + if(duration > 0) { + this.duration = unit.toMillis(duration); + } else { + this.duration = -1; + } + } + + // non-javadoc, see interface ConnectionReleaseTrigger + public void releaseConnection() { + if (connManager != null) { + connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS); + } + } + + // non-javadoc, see interface ConnectionReleaseTrigger + public void abortConnection() { + if (aborted) { + return; + } + aborted = true; + unmarkReusable(); + try { + shutdown(); + } catch (IOException ignore) { + } + // Usually #abortConnection() is expected to be called from + // a helper thread in order to unblock the main execution thread + // blocked in an I/O operation. It may be unsafe to call + // #releaseConnection() from the helper thread, so we have to rely + // on an IOException thrown by the closed socket on the main thread + // to trigger the release of the connection back to the + // connection manager. + // + // However, if this method is called from the main execution thread + // it should be safe to release the connection immediately. Besides, + // this also helps ensure the connection gets released back to the + // manager if #abortConnection() is called from the main execution + // thread while there is no blocking I/O operation. + if (executionThread.equals(Thread.currentThread())) { + releaseConnection(); + } + } + +} // class AbstractClientConnAdapter diff --git a/src/org/apache/http/impl/conn/AbstractPoolEntry.java b/src/org/apache/http/impl/conn/AbstractPoolEntry.java new file mode 100644 index 0000000..0e7d95f --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractPoolEntry.java @@ -0,0 +1,322 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractPoolEntry.java $ + * $Revision: 658775 $ + * $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.RouteTracker; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.OperatedClientConnection; + + + +/** + * A pool entry for use by connection manager implementations. + * Pool entries work in conjunction with an + * {@link AbstractClientConnAdapter adapter}. + * The adapter is handed out to applications that obtain a connection. + * The pool entry stores the underlying connection and tracks the + * {@link HttpRoute route} established. + * The adapter delegates methods for establishing the route to + * it's pool entry. + * <br/> + * If the managed connections is released or revoked, the adapter + * gets disconnected, but the pool entry still contains the + * underlying connection and the established route. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 658775 $ + * + * @since 4.0 + */ +public abstract class AbstractPoolEntry { + + /** The connection operator. */ + protected final ClientConnectionOperator connOperator; + + /** The underlying connection being pooled or used. */ + protected final OperatedClientConnection connection; + + /** The route for which this entry gets allocated. */ + //@@@ currently accessed from connection manager(s) as attribute + //@@@ avoid that, derived classes should decide whether update is allowed + //@@@ SCCM: yes, TSCCM: no + protected volatile HttpRoute route; + + /** Connection state object */ + protected volatile Object state; + + /** The tracked route, or <code>null</code> before tracking starts. */ + protected volatile RouteTracker tracker; + + + /** + * Creates a new pool entry. + * + * @param connOperator the Connection Operator for this entry + * @param route the planned route for the connection, + * or <code>null</code> + */ + protected AbstractPoolEntry(ClientConnectionOperator connOperator, + HttpRoute route) { + super(); + if (connOperator == null) { + throw new IllegalArgumentException("Connection operator may not be null"); + } + this.connOperator = connOperator; + this.connection = connOperator.createConnection(); + this.route = route; + this.tracker = null; + } + + /** + * Returns the state object associated with this pool entry. + * + * @return The state object + */ + public Object getState() { + return state; + } + + /** + * Assigns a state object to this pool entry. + * + * @param state The state object + */ + public void setState(final Object state) { + this.state = state; + } + + /** + * Opens the underlying connection. + * + * @param route the route along which to open the connection + * @param context the context for opening the connection + * @param params the parameters for opening the connection + * + * @throws IOException in case of a problem + */ + public void open(HttpRoute route, + HttpContext context, HttpParams params) + throws IOException { + + if (route == null) { + throw new IllegalArgumentException + ("Route must not be null."); + } + //@@@ is context allowed to be null? depends on operator? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if ((this.tracker != null) && this.tracker.isConnected()) { + throw new IllegalStateException("Connection already open."); + } + + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // opening of the connection will be tracked. + + //@@@ verify route against planned route? + + this.tracker = new RouteTracker(route); + final HttpHost proxy = route.getProxyHost(); + + connOperator.openConnection + (this.connection, + (proxy != null) ? proxy : route.getTargetHost(), + route.getLocalAddress(), + context, params); + + RouteTracker localTracker = tracker; // capture volatile + + // If this tracker was reset while connecting, + // fail early. + if (localTracker == null) { + throw new IOException("Request aborted"); + } + + if (proxy == null) { + localTracker.connectTarget(this.connection.isSecure()); + } else { + localTracker.connectProxy(proxy, this.connection.isSecure()); + } + + } // open + + + /** + * Tracks tunnelling of the connection to the target. + * The tunnel has to be established outside by sending a CONNECT + * request to the (last) proxy. + * + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelTarget(boolean secure, HttpParams params) + throws IOException { + + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + //@@@ check for proxy in planned route? + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + if (this.tracker.isTunnelled()) { + throw new IllegalStateException + ("Connection is already tunnelled."); + } + + // LOG.debug? + + this.connection.update(null, tracker.getTargetHost(), + secure, params); + this.tracker.tunnelTarget(secure); + + } // tunnelTarget + + + /** + * Tracks tunnelling of the connection to a chained proxy. + * The tunnel has to be established outside by sending a CONNECT + * request to the previous proxy. + * + * @param next the proxy to which the tunnel was established. + * See {@link org.apache.http.conn.ManagedClientConnection#tunnelProxy + * ManagedClientConnection.tunnelProxy} + * for details. + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) + throws IOException { + + if (next == null) { + throw new IllegalArgumentException + ("Next proxy must not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + //@@@ check for proxy in planned route? + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + + // LOG.debug? + + this.connection.update(null, next, secure, params); + this.tracker.tunnelProxy(next, secure); + + } // tunnelProxy + + + /** + * Layers a protocol on top of an established tunnel. + * + * @param context the context for layering + * @param params the parameters for layering + * + * @throws IOException in case of a problem + */ + public void layerProtocol(HttpContext context, HttpParams params) + throws IOException { + + //@@@ is context allowed to be null? depends on operator? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + if (!this.tracker.isTunnelled()) { + //@@@ allow this? + throw new IllegalStateException + ("Protocol layering without a tunnel not supported."); + } + if (this.tracker.isLayered()) { + throw new IllegalStateException + ("Multiple protocol layering not supported."); + } + + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // layering on top of the connection will be tracked. + + final HttpHost target = tracker.getTargetHost(); + + connOperator.updateSecureConnection(this.connection, target, + context, params); + + this.tracker.layerProtocol(this.connection.isSecure()); + + } // layerProtocol + + + /** + * Shuts down the entry. + * + * If {@link #open(HttpRoute, HttpContext, HttpParams)} is in progress, + * this will cause that open to possibly throw an {@link IOException}. + */ + protected void shutdownEntry() { + tracker = null; + } + + +} // class AbstractPoolEntry + diff --git a/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java b/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java new file mode 100644 index 0000000..2c5fd30 --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java @@ -0,0 +1,188 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractPooledConnAdapter.java $ + * $Revision: 658775 $ + * $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.OperatedClientConnection; + + + +/** + * Abstract adapter from pool {@link AbstractPoolEntry entries} to + * {@link org.apache.http.conn.ManagedClientConnection managed} + * client connections. + * The connection in the pool entry is used to initialize the base class. + * In addition, methods to establish a route are delegated to the + * pool entry. {@link #shutdown shutdown} and {@link #close close} + * will clear the tracked route in the pool entry and call the + * respective method of the wrapped connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 658775 $ $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * @since 4.0 + */ +public abstract class AbstractPooledConnAdapter extends AbstractClientConnAdapter { + + /** The wrapped pool entry. */ + protected volatile AbstractPoolEntry poolEntry; + + + /** + * Creates a new connection adapter. + * + * @param manager the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected AbstractPooledConnAdapter(ClientConnectionManager manager, + AbstractPoolEntry entry) { + super(manager, entry.connection); + this.poolEntry = entry; + } + + + /** + * Asserts that this adapter is still attached. + * + * @throws IllegalStateException + * if it is {@link #detach detach}ed + */ + protected final void assertAttached() { + if (poolEntry == null) { + throw new IllegalStateException("Adapter is detached."); + } + } + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + @Override + protected void detach() { + super.detach(); + poolEntry = null; + } + + + // non-javadoc, see interface ManagedHttpConnection + public HttpRoute getRoute() { + + assertAttached(); + return (poolEntry.tracker == null) ? + null : poolEntry.tracker.toRoute(); + } + + // non-javadoc, see interface ManagedHttpConnection + public void open(HttpRoute route, + HttpContext context, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.open(route, context, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void tunnelTarget(boolean secure, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.tunnelTarget(secure, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.tunnelProxy(next, secure, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void layerProtocol(HttpContext context, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.layerProtocol(context, params); + } + + + + // non-javadoc, see interface HttpConnection + public void close() throws IOException { + if (poolEntry != null) + poolEntry.shutdownEntry(); + + OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.close(); + } + } + + // non-javadoc, see interface HttpConnection + public void shutdown() throws IOException { + if (poolEntry != null) + poolEntry.shutdownEntry(); + + OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.shutdown(); + } + } + + + // non-javadoc, see interface ManagedClientConnection + public Object getState() { + assertAttached(); + return poolEntry.getState(); + } + + + // non-javadoc, see interface ManagedClientConnection + public void setState(final Object state) { + assertAttached(); + poolEntry.setState(state); + } + + +} // class AbstractPooledConnAdapter diff --git a/src/org/apache/http/impl/conn/DefaultClientConnection.java b/src/org/apache/http/impl/conn/DefaultClientConnection.java new file mode 100644 index 0000000..a41f57a --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultClientConnection.java @@ -0,0 +1,259 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultClientConnection.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; +import java.net.Socket; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.params.HttpParams; +import org.apache.http.impl.SocketHttpClientConnection; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; + +import org.apache.http.conn.OperatedClientConnection; + + +/** + * Default implementation of an operated client connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * @since 4.0 + */ +public class DefaultClientConnection extends SocketHttpClientConnection + implements OperatedClientConnection { + + private final Log log = LogFactory.getLog(getClass()); + private final Log headerLog = LogFactory.getLog("org.apache.http.headers"); + private final Log wireLog = LogFactory.getLog("org.apache.http.wire"); + + /** The unconnected socket */ + private volatile Socket socket; + + /** The target host of this connection. */ + private HttpHost targetHost; + + /** Whether this connection is secure. */ + private boolean connSecure; + + /** True if this connection was shutdown. */ + private volatile boolean shutdown; + + public DefaultClientConnection() { + super(); + } + + + // non-javadoc, see interface OperatedClientConnection + public final HttpHost getTargetHost() { + return this.targetHost; + } + + + // non-javadoc, see interface OperatedClientConnection + public final boolean isSecure() { + return this.connSecure; + } + + + @Override + public final Socket getSocket() { + return this.socket; + } + + + public void opening(Socket sock, HttpHost target) throws IOException { + assertNotOpen(); + this.socket = sock; + this.targetHost = target; + + // Check for shutdown after assigning socket, so that + if (this.shutdown) { + sock.close(); // allow this to throw... + // ...but if it doesn't, explicitly throw one ourselves. + throw new IOException("Connection already shutdown"); + } + } + + + public void openCompleted(boolean secure, HttpParams params) throws IOException { + assertNotOpen(); + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + this.connSecure = secure; + bind(this.socket, params); + } + + /** + * Force-closes this connection. + * If the connection is still in the process of being open (the method + * {@link #opening opening} was already called but + * {@link #openCompleted openCompleted} was not), the associated + * socket that is being connected to a remote address will be closed. + * That will interrupt a thread that is blocked on connecting + * the socket. + * If the connection is not yet open, this will prevent the connection + * from being opened. + * + * @throws IOException in case of a problem + */ + @Override + public void shutdown() throws IOException { + log.debug("Connection shut down"); + shutdown = true; + + super.shutdown(); + Socket sock = this.socket; // copy volatile attribute + if (sock != null) + sock.close(); + + } // shutdown + + + @Override + public void close() throws IOException { + log.debug("Connection closed"); + super.close(); + } + + + @Override + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + SessionInputBuffer inbuffer = super.createSessionInputBuffer( + socket, + buffersize, + params); + if (wireLog.isDebugEnabled()) { + inbuffer = new LoggingSessionInputBuffer(inbuffer, new Wire(wireLog)); + } + return inbuffer; + } + + + @Override + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + SessionOutputBuffer outbuffer = super.createSessionOutputBuffer( + socket, + buffersize, + params); + if (wireLog.isDebugEnabled()) { + outbuffer = new LoggingSessionOutputBuffer(outbuffer, new Wire(wireLog)); + } + return outbuffer; + } + + + @Override + protected HttpMessageParser createResponseParser( + final SessionInputBuffer buffer, + final HttpResponseFactory responseFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new DefaultResponseParser + (buffer, null, responseFactory, params); + } + + + // non-javadoc, see interface OperatedClientConnection + public void update(Socket sock, HttpHost target, + boolean secure, HttpParams params) + throws IOException { + + assertOpen(); + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + if (sock != null) { + this.socket = sock; + bind(sock, params); + } + targetHost = target; + connSecure = secure; + + } // update + + + @Override + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + HttpResponse response = super.receiveResponseHeader(); + if (headerLog.isDebugEnabled()) { + headerLog.debug("<< " + response.getStatusLine().toString()); + Header[] headers = response.getAllHeaders(); + for (Header header : headers) { + headerLog.debug("<< " + header.toString()); + } + } + return response; + } + + + @Override + public void sendRequestHeader(HttpRequest request) throws HttpException, IOException { + super.sendRequestHeader(request); + if (headerLog.isDebugEnabled()) { + headerLog.debug(">> " + request.getRequestLine().toString()); + Header[] headers = request.getAllHeaders(); + for (Header header : headers) { + headerLog.debug(">> " + header.toString()); + } + } + } + +} // class DefaultClientConnection diff --git a/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java b/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java new file mode 100644 index 0000000..41488e1 --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java @@ -0,0 +1,216 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java $ + * $Revision: 652193 $ + * $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.Socket; +import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.scheme.LayeredSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.scheme.SocketFactory; + + +/** + * Default implementation of a + * {@link ClientConnectionOperator ClientConnectionOperator}. + * It uses a {@link SchemeRegistry SchemeRegistry} to look up + * {@link SocketFactory SocketFactory} objects. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 652193 $ $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $ + * + * @since 4.0 + */ +public class DefaultClientConnectionOperator + implements ClientConnectionOperator { + + + /** The scheme registry for looking up socket factories. */ + protected SchemeRegistry schemeRegistry; + + + /** + * Creates a new client connection operator for the given scheme registry. + * + * @param schemes the scheme registry + */ + public DefaultClientConnectionOperator(SchemeRegistry schemes) { + if (schemes == null) { + throw new IllegalArgumentException + ("Scheme registry must not be null."); + } + schemeRegistry = schemes; + } + + + // non-javadoc, see interface ClientConnectionOperator + public OperatedClientConnection createConnection() { + return new DefaultClientConnection(); + } + + + // non-javadoc, see interface ClientConnectionOperator + public void openConnection(OperatedClientConnection conn, + HttpHost target, + InetAddress local, + HttpContext context, + HttpParams params) + throws IOException { + + if (conn == null) { + throw new IllegalArgumentException + ("Connection must not be null."); + } + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + // local address may be null + //@@@ is context allowed to be null? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if (conn.isOpen()) { + throw new IllegalArgumentException + ("Connection must not be open."); + } + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + final SocketFactory sf = schm.getSocketFactory(); + + Socket sock = sf.createSocket(); + conn.opening(sock, target); + + try { + sock = sf.connectSocket(sock, target.getHostName(), + schm.resolvePort(target.getPort()), + local, 0, params); + } catch (ConnectException ex) { + throw new HttpHostConnectException(target, ex); + } + prepareSocket(sock, context, params); + conn.openCompleted(sf.isSecure(sock), params); + } // openConnection + + + // non-javadoc, see interface ClientConnectionOperator + public void updateSecureConnection(OperatedClientConnection conn, + HttpHost target, + HttpContext context, + HttpParams params) + throws IOException { + + + if (conn == null) { + throw new IllegalArgumentException + ("Connection must not be null."); + } + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + //@@@ is context allowed to be null? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if (!conn.isOpen()) { + throw new IllegalArgumentException + ("Connection must be open."); + } + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + if (!(schm.getSocketFactory() instanceof LayeredSocketFactory)) { + throw new IllegalArgumentException + ("Target scheme (" + schm.getName() + + ") must have layered socket factory."); + } + + final LayeredSocketFactory lsf = (LayeredSocketFactory) schm.getSocketFactory(); + final Socket sock; + try { + sock = lsf.createSocket + (conn.getSocket(), target.getHostName(), target.getPort(), true); + } catch (ConnectException ex) { + throw new HttpHostConnectException(target, ex); + } + prepareSocket(sock, context, params); + conn.update(sock, target, lsf.isSecure(sock), params); + //@@@ error handling: close the layered socket in case of exception? + + } // updateSecureConnection + + + /** + * Performs standard initializations on a newly created socket. + * + * @param sock the socket to prepare + * @param context the context for the connection + * @param params the parameters from which to prepare the socket + * + * @throws IOException in case of an IO problem + */ + protected void prepareSocket(Socket sock, HttpContext context, + HttpParams params) + throws IOException { + + // context currently not used, but derived classes may need it + //@@@ is context allowed to be null? + + sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + sock.setSoLinger(linger > 0, linger); + } + + } // prepareSocket + + +} // class DefaultClientConnectionOperator + diff --git a/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java b/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java new file mode 100644 index 0000000..90fd55f --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java @@ -0,0 +1,121 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.net.InetAddress; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; + +import org.apache.http.conn.params.ConnRouteParams; + + +/** + * Default implementation of an {@link HttpRoutePlanner}. + * This implementation is based on + * {@link org.apache.http.conn.params.ConnRoutePNames parameters}. + * It will not make use of any Java system properties, + * nor of system or browser proxy settings. + */ +public class DefaultHttpRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected SchemeRegistry schemeRegistry; + + + /** + * Creates a new default route planner. + * + * @param schreg the scheme registry + */ + public DefaultHttpRoutePlanner(SchemeRegistry schreg) { + if (schreg == null) { + throw new IllegalArgumentException + ("SchemeRegistry must not be null."); + } + schemeRegistry = schreg; + } + + + // non-javadoc, see interface HttpRoutePlanner + public HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (request == null) { + throw new IllegalStateException + ("Request must not be null."); + } + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) + return route; + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + if (target == null) { + throw new IllegalStateException + ("Target host must not be null."); + } + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = + ConnRouteParams.getDefaultProxy(request.getParams()); + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + + +} diff --git a/src/org/apache/http/impl/conn/DefaultResponseParser.java b/src/org/apache/http/impl/conn/DefaultResponseParser.java new file mode 100644 index 0000000..f817a10 --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultResponseParser.java @@ -0,0 +1,103 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultResponseParser.java $ + * $Revision: 617638 $ + * $Date: 2008-02-01 12:49:26 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpResponseFactory; +import org.apache.http.NoHttpResponseException; +import org.apache.http.ProtocolException; +import org.apache.http.StatusLine; +import org.apache.http.conn.params.ConnConnectionPNames; +import org.apache.http.impl.io.AbstractMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class DefaultResponseParser extends AbstractMessageParser { + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + private final int maxGarbageLines; + + public DefaultResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + if (responseFactory == null) { + throw new IllegalArgumentException + ("Response factory may not be null"); + } + this.responseFactory = responseFactory; + this.lineBuf = new CharArrayBuffer(128); + this.maxGarbageLines = params.getIntParameter( + ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, Integer.MAX_VALUE); + } + + + @Override + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) throws IOException, HttpException { + // clear the buffer + this.lineBuf.clear(); + //read out the HTTP status string + int count = 0; + ParserCursor cursor = null; + do { + int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1 && count == 0) { + // The server just dropped connection on us + throw new NoHttpResponseException("The target server failed to respond"); + } + cursor = new ParserCursor(0, this.lineBuf.length()); + if (lineParser.hasProtocolVersion(this.lineBuf, cursor)) { + // Got one + break; + } else if (i == -1 || count >= this.maxGarbageLines) { + // Giving up + throw new ProtocolException("The server failed to respond with a " + + "valid HTTP response"); + } + count++; + } while(true); + //create the status line from the status string + StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/src/org/apache/http/impl/conn/IdleConnectionHandler.java b/src/org/apache/http/impl/conn/IdleConnectionHandler.java new file mode 100644 index 0000000..2cacda3 --- /dev/null +++ b/src/org/apache/http/impl/conn/IdleConnectionHandler.java @@ -0,0 +1,190 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpConnection; + + +/** + * A helper class for connection managers to track idle connections. + * + * <p>This class is not synchronized.</p> + * + * @see org.apache.http.conn.ClientConnectionManager#closeIdleConnections + * + * @since 4.0 + */ +public class IdleConnectionHandler { + + private final Log log = LogFactory.getLog(getClass()); + + /** Holds connections and the time they were added. */ + private final Map<HttpConnection,TimeValues> connectionToTimes; + + + public IdleConnectionHandler() { + super(); + connectionToTimes = new HashMap<HttpConnection,TimeValues>(); + } + + /** + * Registers the given connection with this handler. The connection will be held until + * {@link #remove} or {@link #closeIdleConnections} is called. + * + * @param connection the connection to add + * + * @see #remove + */ + public void add(HttpConnection connection, long validDuration, TimeUnit unit) { + + Long timeAdded = Long.valueOf(System.currentTimeMillis()); + + if (log.isDebugEnabled()) { + log.debug("Adding connection at: " + timeAdded); + } + + connectionToTimes.put(connection, new TimeValues(timeAdded, validDuration, unit)); + } + + /** + * Removes the given connection from the list of connections to be closed when idle. + * This will return true if the connection is still valid, and false + * if the connection should be considered expired and not used. + * + * @param connection + * @return True if the connection is still valid. + */ + public boolean remove(HttpConnection connection) { + TimeValues times = connectionToTimes.remove(connection); + if(times == null) { + log.warn("Removing a connection that never existed!"); + return true; + } else { + return System.currentTimeMillis() <= times.timeExpires; + } + } + + /** + * Removes all connections referenced by this handler. + */ + public void removeAll() { + this.connectionToTimes.clear(); + } + + /** + * Closes connections that have been idle for at least the given amount of time. + * + * @param idleTime the minimum idle time, in milliseconds, for connections to be closed + */ + //@@@ add TimeUnit argument here? + public void closeIdleConnections(long idleTime) { + + // the latest time for which connections will be closed + long idleTimeout = System.currentTimeMillis() - idleTime; + + if (log.isDebugEnabled()) { + log.debug("Checking for connections, idleTimeout: " + idleTimeout); + } + + Iterator<HttpConnection> connectionIter = + connectionToTimes.keySet().iterator(); + + while (connectionIter.hasNext()) { + HttpConnection conn = connectionIter.next(); + TimeValues times = connectionToTimes.get(conn); + Long connectionTime = times.timeAdded; + if (connectionTime.longValue() <= idleTimeout) { + if (log.isDebugEnabled()) { + log.debug("Closing connection, connection time: " + connectionTime); + } + connectionIter.remove(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + + public void closeExpiredConnections() { + long now = System.currentTimeMillis(); + if (log.isDebugEnabled()) { + log.debug("Checking for expired connections, now: " + now); + } + + Iterator<HttpConnection> connectionIter = + connectionToTimes.keySet().iterator(); + + while (connectionIter.hasNext()) { + HttpConnection conn = connectionIter.next(); + TimeValues times = connectionToTimes.get(conn); + if(times.timeExpires <= now) { + if (log.isDebugEnabled()) { + log.debug("Closing connection, expired @: " + times.timeExpires); + } + connectionIter.remove(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + private static class TimeValues { + private final long timeAdded; + private final long timeExpires; + + /** + * @param now The current time in milliseconds + * @param validDuration The duration this connection is valid for + * @param validUnit The unit of time the duration is specified in. + */ + TimeValues(long now, long validDuration, TimeUnit validUnit) { + this.timeAdded = now; + if(validDuration > 0) { + this.timeExpires = now + validUnit.toMillis(validDuration); + } else { + this.timeExpires = Long.MAX_VALUE; + } + } + } +} diff --git a/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java b/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java new file mode 100644 index 0000000..4f6477e --- /dev/null +++ b/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java @@ -0,0 +1,117 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/LoggingSessionInputBuffer.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Logs all data read to the wire LOG. + * + * @author Ortwin Glueck + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class LoggingSessionInputBuffer implements SessionInputBuffer { + + /** Original session input buffer. */ + private final SessionInputBuffer in; + + /** The wire log to use for writing. */ + private final Wire wire; + + /** + * Create an instance that wraps the specified session input buffer. + * @param in The session input buffer. + * @param wire The wire log to use. + */ + public LoggingSessionInputBuffer(final SessionInputBuffer in, final Wire wire) { + super(); + this.in = in; + this.wire = wire; + } + + public boolean isDataAvailable(int timeout) throws IOException { + return this.in.isDataAvailable(timeout); + } + + public int read(byte[] b, int off, int len) throws IOException { + int l = this.in.read(b, off, len); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, off, l); + } + return l; + } + + public int read() throws IOException { + int l = this.in.read(); + if (this.wire.enabled() && l > 0) { + this.wire.input(l); + } + return l; + } + + public int read(byte[] b) throws IOException { + int l = this.in.read(b); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, 0, l); + } + return l; + } + + public String readLine() throws IOException { + String s = this.in.readLine(); + if (this.wire.enabled() && s != null) { + this.wire.input(s + "[EOL]"); + } + return s; + } + + public int readLine(final CharArrayBuffer buffer) throws IOException { + int l = this.in.readLine(buffer); + if (this.wire.enabled() && l > 0) { + int pos = buffer.length() - l; + String s = new String(buffer.buffer(), pos, l); + this.wire.input(s + "[EOL]"); + } + return l; + } + + public HttpTransportMetrics getMetrics() { + return this.in.getMetrics(); + } + +} diff --git a/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java b/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java new file mode 100644 index 0000000..1afab7d --- /dev/null +++ b/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java @@ -0,0 +1,109 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Logs all data written to the wire LOG. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class LoggingSessionOutputBuffer implements SessionOutputBuffer { + + /** Original data transmitter. */ + private final SessionOutputBuffer out; + + /** The wire log to use. */ + private final Wire wire; + + /** + * Create an instance that wraps the specified session output buffer. + * @param out The session output buffer. + * @param wire The Wire log to use. + */ + public LoggingSessionOutputBuffer(final SessionOutputBuffer out, final Wire wire) { + super(); + this.out = out; + this.wire = wire; + } + + public void write(byte[] b, int off, int len) throws IOException { + this.out.write(b, off, len); + if (this.wire.enabled()) { + this.wire.output(b, off, len); + } + } + + public void write(int b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void write(byte[] b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void writeLine(final CharArrayBuffer buffer) throws IOException { + this.out.writeLine(buffer); + if (this.wire.enabled()) { + String s = new String(buffer.buffer(), 0, buffer.length()); + this.wire.output(s + "[EOL]"); + } + } + + public void writeLine(final String s) throws IOException { + this.out.writeLine(s); + if (this.wire.enabled()) { + this.wire.output(s + "[EOL]"); + } + } + + public HttpTransportMetrics getMetrics() { + return this.out.getMetrics(); + } + +} diff --git a/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java b/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java new file mode 100644 index 0000000..136caf4 --- /dev/null +++ b/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java @@ -0,0 +1,290 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; + +import org.apache.http.conn.params.ConnRouteParams; + + +/** + * Default implementation of an {@link HttpRoutePlanner}. + * This implementation is based on {@link java.net.ProxySelector}. + * By default, it will pick up the proxy settings of the JVM, either + * from system properties or from the browser running the application. + * Additionally, it interprets some + * {@link org.apache.http.conn.params.ConnRoutePNames parameters}, + * though not the {@link + * org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY}. + */ +public class ProxySelectorRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected SchemeRegistry schemeRegistry; + + /** The proxy selector to use, or <code>null</code> for system default. */ + protected ProxySelector proxySelector; + + + /** + * Creates a new proxy selector route planner. + * + * @param schreg the scheme registry + * @param prosel the proxy selector, or + * <code>null</code> for the system default + */ + public ProxySelectorRoutePlanner(SchemeRegistry schreg, + ProxySelector prosel) { + + if (schreg == null) { + throw new IllegalArgumentException + ("SchemeRegistry must not be null."); + } + schemeRegistry = schreg; + proxySelector = prosel; + } + + + /** + * Obtains the proxy selector to use. + * + * @return the proxy selector, or <code>null</code> for the system default + */ + public ProxySelector getProxySelector() { + return this.proxySelector; + } + + + /** + * Sets the proxy selector to use. + * + * @param prosel the proxy selector, or + * <code>null</code> to use the system default + */ + public void setProxySelector(ProxySelector prosel) { + this.proxySelector = prosel; + } + + + + // non-javadoc, see interface HttpRoutePlanner + public HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (request == null) { + throw new IllegalStateException + ("Request must not be null."); + } + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) + return route; + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + if (target == null) { + throw new IllegalStateException + ("Target host must not be null."); + } + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = determineProxy(target, request, context); + + final Scheme schm = + this.schemeRegistry.getScheme(target.getSchemeName()); + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + + + /** + * Determines a proxy for the given target. + * + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return the proxy to use, or <code>null</code> for a direct route + * + * @throws HttpException + * in case of system proxy settings that cannot be handled + */ + protected HttpHost determineProxy(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + // the proxy selector can be 'unset', so we better deal with null here + ProxySelector psel = this.proxySelector; + if (psel == null) + psel = ProxySelector.getDefault(); + if (psel == null) + return null; + + URI targetURI = null; + try { + targetURI = new URI(target.toURI()); + } catch (URISyntaxException usx) { + throw new HttpException + ("Cannot convert host to URI: " + target, usx); + } + List<Proxy> proxies = psel.select(targetURI); + + Proxy p = chooseProxy(proxies, target, request, context); + + HttpHost result = null; + if (p.type() == Proxy.Type.HTTP) { + // convert the socket address to an HttpHost + if (!(p.address() instanceof InetSocketAddress)) { + throw new HttpException + ("Unable to handle non-Inet proxy address: "+p.address()); + } + final InetSocketAddress isa = (InetSocketAddress) p.address(); + // assume default scheme (http) + result = new HttpHost(getHost(isa), isa.getPort()); + } + + return result; + } + + + /** + * Obtains a host from an {@link InetSocketAddress}. + * + * @param isa the socket address + * + * @return a host string, either as a symbolic name or + * as a literal IP address string + * <br/> + * (TODO: determine format for IPv6 addresses, with or without [brackets]) + */ + protected String getHost(InetSocketAddress isa) { + + //@@@ Will this work with literal IPv6 addresses, or do we + //@@@ need to wrap these in [] for the string representation? + //@@@ Having it in this method at least allows for easy workarounds. + return isa.isUnresolved() ? + isa.getHostName() : isa.getAddress().getHostAddress(); + + } + + + /* + * Chooses a proxy from a list of available proxies. + * The default implementation just picks the first non-SOCKS proxy + * from the list. If there are only SOCKS proxies, + * {@link Proxy#NO_PROXY Proxy.NO_PROXY} is returned. + * Derived classes may implement more advanced strategies, + * such as proxy rotation if there are multiple options. + * + * @param proxies the list of proxies to choose from, + * never <code>null</code> or empty + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return a proxy of type {@link Proxy.Type#DIRECT DIRECT} + * or {@link Proxy.Type#HTTP HTTP}, never <code>null</code> + */ + protected Proxy chooseProxy(List<Proxy> proxies, + HttpHost target, + HttpRequest request, + HttpContext context) { + + if ((proxies == null) || proxies.isEmpty()) { + throw new IllegalArgumentException + ("Proxy list must not be empty."); + } + + Proxy result = null; + + // check the list for one we can use + for (int i=0; (result == null) && (i < proxies.size()); i++) { + + Proxy p = proxies.get(i); + switch (p.type()) { + + case DIRECT: + case HTTP: + result = p; + break; + + case SOCKS: + // SOCKS hosts are not handled on the route level. + // The socket may make use of the SOCKS host though. + break; + } + } + + if (result == null) { + //@@@ log as warning or info that only a socks proxy is available? + // result can only be null if all proxies are socks proxies + // socks proxies are not handled on the route planning level + result = Proxy.NO_PROXY; + } + + return result; + } + +} // class ProxySelectorRoutePlanner + diff --git a/src/org/apache/http/impl/conn/SingleClientConnManager.java b/src/org/apache/http/impl/conn/SingleClientConnManager.java new file mode 100644 index 0000000..7999f3e --- /dev/null +++ b/src/org/apache/http/impl/conn/SingleClientConnManager.java @@ -0,0 +1,444 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.RouteTracker; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.params.HttpParams; + + +/** + * A connection "manager" for a single connection. + * This manager is good only for single-threaded use. + * Allocation <i>always</i> returns the connection immediately, + * even if it has not been released after the previous allocation. + * In that case, a {@link #MISUSE_MESSAGE warning} is logged + * and the previously issued connection is revoked. + * <p> + * This class is derived from <code>SimpleHttpConnectionManager</code> + * in HttpClient 3. See there for original authors. + * </p> + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class SingleClientConnManager implements ClientConnectionManager { + + private final Log log = LogFactory.getLog(getClass()); + + /** The message to be logged on multiple allocation. */ + public final static String MISUSE_MESSAGE = + "Invalid use of SingleClientConnManager: connection still allocated.\n" + + "Make sure to release the connection before allocating another one."; + + + /** The schemes supported by this connection manager. */ + protected SchemeRegistry schemeRegistry; + + /** The operator for opening and updating connections. */ + protected ClientConnectionOperator connOperator; + + /** The one and only entry in this pool. */ + protected PoolEntry uniquePoolEntry; + + /** The currently issued managed connection, if any. */ + protected ConnAdapter managedConn; + + /** The time of the last connection release, or -1. */ + protected long lastReleaseTime; + + /** The time the last released connection expires and shouldn't be reused. */ + protected long connectionExpiresTime; + + /** Whether the connection should be shut down on release. */ + protected boolean alwaysShutDown; + + /** Indicates whether this connection manager is shut down. */ + protected volatile boolean isShutDown; + + + + + /** + * Creates a new simple connection manager. + * + * @param params the parameters for this manager + * @param schreg the scheme registry, or + * <code>null</code> for the default registry + */ + public SingleClientConnManager(HttpParams params, + SchemeRegistry schreg) { + + if (schreg == null) { + throw new IllegalArgumentException + ("Scheme registry must not be null."); + } + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + this.uniquePoolEntry = new PoolEntry(); + this.managedConn = null; + this.lastReleaseTime = -1L; + this.alwaysShutDown = false; //@@@ from params? as argument? + this.isShutDown = false; + + } // <constructor> + + + @Override + protected void finalize() throws Throwable { + shutdown(); + super.finalize(); + } + + + // non-javadoc, see interface ClientConnectionManager + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry to use, or <code>null</code> + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(SchemeRegistry schreg) { + + return new DefaultClientConnectionOperator(schreg); + } + + + /** + * Asserts that this manager is not shut down. + * + * @throws IllegalStateException if this manager is shut down + */ + protected final void assertStillUp() + throws IllegalStateException { + + if (this.isShutDown) + throw new IllegalStateException("Manager is shut down."); + } + + + public final ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + return new ClientConnectionRequest() { + + public void abortRequest() { + // Nothing to abort, since requests are immediate. + } + + public ManagedClientConnection getConnection( + long timeout, TimeUnit tunit) { + return SingleClientConnManager.this.getConnection( + route, state); + } + + }; + } + + + /** + * Obtains a connection. + * This method does not block. + * + * @param route where the connection should point to + * + * @return a connection that can be used to communicate + * along the given route + */ + public ManagedClientConnection getConnection(HttpRoute route, Object state) { + + if (route == null) { + throw new IllegalArgumentException("Route may not be null."); + } + assertStillUp(); + + if (log.isDebugEnabled()) { + log.debug("Get connection for route " + route); + } + + if (managedConn != null) + revokeConnection(); + + // check re-usability of the connection + boolean recreate = false; + boolean shutdown = false; + + // Kill the connection if it expired. + closeExpiredConnections(); + + if (uniquePoolEntry.connection.isOpen()) { + RouteTracker tracker = uniquePoolEntry.tracker; + shutdown = (tracker == null || // can happen if method is aborted + !tracker.toRoute().equals(route)); + } else { + // If the connection is not open, create a new PoolEntry, + // as the connection may have been marked not reusable, + // due to aborts -- and the PoolEntry should not be reused + // either. There's no harm in recreating an entry if + // the connection is closed. + recreate = true; + } + + if (shutdown) { + recreate = true; + try { + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + log.debug("Problem shutting down connection.", iox); + } + } + + if (recreate) + uniquePoolEntry = new PoolEntry(); + + managedConn = new ConnAdapter(uniquePoolEntry, route); + + return managedConn; + } + + + // non-javadoc, see interface ClientConnectionManager + public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { + assertStillUp(); + + if (!(conn instanceof ConnAdapter)) { + throw new IllegalArgumentException + ("Connection class mismatch, " + + "connection not obtained from this manager."); + } + + if (log.isDebugEnabled()) { + log.debug("Releasing connection " + conn); + } + + ConnAdapter sca = (ConnAdapter) conn; + if (sca.poolEntry == null) + return; // already released + ClientConnectionManager manager = sca.getManager(); + if (manager != null && manager != this) { + throw new IllegalArgumentException + ("Connection not obtained from this manager."); + } + + try { + // make sure that the response has been read completely + if (sca.isOpen() && (this.alwaysShutDown || + !sca.isMarkedReusable()) + ) { + if (log.isDebugEnabled()) { + log.debug + ("Released connection open but not reusable."); + } + + // make sure this connection will not be re-used + // we might have gotten here because of a shutdown trigger + // shutdown of the adapter also clears the tracked route + sca.shutdown(); + } + } catch (IOException iox) { + //@@@ log as warning? let pass? + if (log.isDebugEnabled()) + log.debug("Exception shutting down released connection.", + iox); + } finally { + sca.detach(); + managedConn = null; + lastReleaseTime = System.currentTimeMillis(); + if(validDuration > 0) + connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; + else + connectionExpiresTime = Long.MAX_VALUE; + } + } // releaseConnection + + public void closeExpiredConnections() { + if(System.currentTimeMillis() >= connectionExpiresTime) { + closeIdleConnections(0, TimeUnit.MILLISECONDS); + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void closeIdleConnections(long idletime, TimeUnit tunit) { + assertStillUp(); + + // idletime can be 0 or negative, no problem there + if (tunit == null) { + throw new IllegalArgumentException("Time unit must not be null."); + } + + if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { + final long cutoff = + System.currentTimeMillis() - tunit.toMillis(idletime); + if (lastReleaseTime <= cutoff) { + try { + uniquePoolEntry.close(); + } catch (IOException iox) { + // ignore + log.debug("Problem closing idle connection.", iox); + } + } + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void shutdown() { + + this.isShutDown = true; + + if (managedConn != null) + managedConn.detach(); + + try { + if (uniquePoolEntry != null) // and connection open? + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + // ignore + log.debug("Problem while shutting down manager.", iox); + } finally { + uniquePoolEntry = null; + } + } + + + /** + * Revokes the currently issued connection. + * The adapter gets disconnected, the connection will be shut down. + */ + protected void revokeConnection() { + if (managedConn == null) + return; + + log.warn(MISUSE_MESSAGE); + + managedConn.detach(); + + try { + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + // ignore + log.debug("Problem while shutting down connection.", iox); + } + } + + + /** + * The pool entry for this connection manager. + */ + protected class PoolEntry extends AbstractPoolEntry { + + /** + * Creates a new pool entry. + * + */ + protected PoolEntry() { + super(SingleClientConnManager.this.connOperator, null); + } + + /** + * Closes the connection in this pool entry. + */ + protected void close() + throws IOException { + + shutdownEntry(); + if (connection.isOpen()) + connection.close(); + } + + + /** + * Shuts down the connection in this pool entry. + */ + protected void shutdown() + throws IOException { + + shutdownEntry(); + if (connection.isOpen()) + connection.shutdown(); + } + + } // class PoolEntry + + + + /** + * The connection adapter used by this manager. + */ + protected class ConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new connection adapter. + * + * @param entry the pool entry for the connection being wrapped + * @param route the planned route for this connection + */ + protected ConnAdapter(PoolEntry entry, HttpRoute route) { + super(SingleClientConnManager.this, entry); + markReusable(); + entry.route = route; + } + + } + + +} // class SingleClientConnManager diff --git a/src/org/apache/http/impl/conn/Wire.java b/src/org/apache/http/impl/conn/Wire.java new file mode 100644 index 0000000..147b7f5 --- /dev/null +++ b/src/org/apache/http/impl/conn/Wire.java @@ -0,0 +1,160 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/Wire.java,v 1.9 2004/06/24 21:39:52 mbecke Exp $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import org.apache.commons.logging.Log; + +/** + * Logs data to the wire LOG. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class Wire { + + private final Log log; + + public Wire(Log log) { + this.log = log; + } + + private void wire(String header, InputStream instream) + throws IOException { + StringBuilder buffer = new StringBuilder(); + int ch; + while ((ch = instream.read()) != -1) { + if (ch == 13) { + buffer.append("[\\r]"); + } else if (ch == 10) { + buffer.append("[\\n]\""); + buffer.insert(0, "\""); + buffer.insert(0, header); + log.debug(buffer.toString()); + buffer.setLength(0); + } else if ((ch < 32) || (ch > 127)) { + buffer.append("[0x"); + buffer.append(Integer.toHexString(ch)); + buffer.append("]"); + } else { + buffer.append((char) ch); + } + } + if (buffer.length() > 0) { + buffer.append('\"'); + buffer.insert(0, '\"'); + buffer.insert(0, header); + log.debug(buffer.toString()); + } + } + + + public boolean enabled() { + return log.isDebugEnabled(); + } + + public void output(InputStream outstream) + throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", outstream); + } + + public void input(InputStream instream) + throws IOException { + if (instream == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", instream); + } + + public void output(byte[] b, int off, int len) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", new ByteArrayInputStream(b, off, len)); + } + + public void input(byte[] b, int off, int len) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", new ByteArrayInputStream(b, off, len)); + } + + public void output(byte[] b) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", new ByteArrayInputStream(b)); + } + + public void input(byte[] b) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", new ByteArrayInputStream(b)); + } + + public void output(int b) + throws IOException { + output(new byte[] {(byte) b}); + } + + public void input(int b) + throws IOException { + input(new byte[] {(byte) b}); + } + + public void output(final String s) + throws IOException { + if (s == null) { + throw new IllegalArgumentException("Output may not be null"); + } + output(s.getBytes()); + } + + public void input(final String s) + throws IOException { + if (s == null) { + throw new IllegalArgumentException("Input may not be null"); + } + input(s.getBytes()); + } +} diff --git a/src/org/apache/http/impl/conn/package.html b/src/org/apache/http/impl/conn/package.html new file mode 100644 index 0000000..54eb3c2 --- /dev/null +++ b/src/org/apache/http/impl/conn/package.html @@ -0,0 +1,5 @@ +<body> + + +</body> + diff --git a/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java new file mode 100644 index 0000000..2b37d72 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java @@ -0,0 +1,332 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.conn.IdleConnectionHandler; + + +/** + * An abstract connection pool. + * It is used by the {@link ThreadSafeClientConnManager}. + * The abstract pool includes a {@link #poolLock}, which is used to + * synchronize access to the internal pool datastructures. + * Don't use <code>synchronized</code> for that purpose! + */ +public abstract class AbstractConnPool implements RefQueueHandler { + + private final Log log = LogFactory.getLog(getClass()); + + /** + * The global lock for this pool. + */ + protected final Lock poolLock; + + + /** + * References to issued connections. + * Objects in this set are of class + * {@link BasicPoolEntryRef BasicPoolEntryRef}, + * and point to the pool entry for the issued connection. + * GCed connections are detected by the missing pool entries. + */ + protected Set<BasicPoolEntryRef> issuedConnections; + + /** The handler for idle connections. */ + protected IdleConnectionHandler idleConnHandler; + + /** The current total number of connections. */ + protected int numConnections; + + /** + * A reference queue to track loss of pool entries to GC. + * The same queue is used to track loss of the connection manager, + * so we cannot specialize the type. + */ + protected ReferenceQueue<Object> refQueue; + + /** A worker (thread) to track loss of pool entries to GC. */ + private RefQueueWorker refWorker; + + + /** Indicates whether this pool is shut down. */ + protected volatile boolean isShutDown; + + /** + * Creates a new connection pool. + */ + protected AbstractConnPool() { + issuedConnections = new HashSet<BasicPoolEntryRef>(); + idleConnHandler = new IdleConnectionHandler(); + + boolean fair = false; //@@@ check parameters to decide + poolLock = new ReentrantLock(fair); + } + + + /** + * Enables connection garbage collection (GC). + * This method must be called immediately after creating the + * connection pool. It is not possible to enable connection GC + * after pool entries have been created. Neither is it possible + * to disable connection GC. + * + * @throws IllegalStateException + * if connection GC is already enabled, or if it cannot be + * enabled because there already are pool entries + */ + public void enableConnectionGC() + throws IllegalStateException { + + if (refQueue != null) { + throw new IllegalStateException("Connection GC already enabled."); + } + poolLock.lock(); + try { + if (numConnections > 0) { //@@@ is this check sufficient? + throw new IllegalStateException("Pool already in use."); + } + } finally { + poolLock.unlock(); + } + + refQueue = new ReferenceQueue<Object>(); + refWorker = new RefQueueWorker(refQueue, this); + Thread t = new Thread(refWorker); //@@@ use a thread factory + t.setDaemon(true); + t.setName("RefQueueWorker@" + this); + t.start(); + } + + + /** + * Obtains a pool entry with a connection within the given timeout. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + public final + BasicPoolEntry getEntry( + HttpRoute route, + Object state, + long timeout, + TimeUnit tunit) + throws ConnectionPoolTimeoutException, InterruptedException { + return requestPoolEntry(route, state).getPoolEntry(timeout, tunit); + } + + /** + * Returns a new {@link PoolEntryRequest}, from which a {@link BasicPoolEntry} + * can be obtained, or the request can be aborted. + */ + public abstract PoolEntryRequest requestPoolEntry(HttpRoute route, Object state); + + + /** + * Returns an entry into the pool. + * The connection of the entry is expected to be in a suitable state, + * either open and re-usable, or closed. The pool will not make any + * attempt to determine whether it can be re-used or not. + * + * @param entry the entry for the connection to release + * @param reusable <code>true</code> if the entry is deemed + * reusable, <code>false</code> otherwise. + * @param validDuration The duration that the entry should remain free and reusable. + * @param timeUnit The unit of time the duration is measured in. + */ + public abstract void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) + ; + + + + // non-javadoc, see interface RefQueueHandler +// BEGIN android-changed + public void handleReference(Reference ref) { +// END android-changed + poolLock.lock(); + try { + + if (ref instanceof BasicPoolEntryRef) { + // check if the GCed pool entry was still in use + //@@@ find a way to detect this without lookup + //@@@ flag in the BasicPoolEntryRef, to be reset when freed? + final boolean lost = issuedConnections.remove(ref); + if (lost) { + final HttpRoute route = + ((BasicPoolEntryRef)ref).getRoute(); + if (log.isDebugEnabled()) { + log.debug("Connection garbage collected. " + route); + } + handleLostEntry(route); + } + } + + } finally { + poolLock.unlock(); + } + } + + + /** + * Handles cleaning up for a lost pool entry with the given route. + * A lost pool entry corresponds to a connection that was + * garbage collected instead of being properly released. + * + * @param route the route of the pool entry that was lost + */ + protected abstract void handleLostEntry(HttpRoute route) + ; + + + /** + * Closes idle connections. + * + * @param idletime the time the connections should have been idle + * in order to be closed now + * @param tunit the unit for the <code>idletime</code> + */ + public void closeIdleConnections(long idletime, TimeUnit tunit) { + + // idletime can be 0 or negative, no problem there + if (tunit == null) { + throw new IllegalArgumentException("Time unit must not be null."); + } + + poolLock.lock(); + try { + idleConnHandler.closeIdleConnections(tunit.toMillis(idletime)); + } finally { + poolLock.unlock(); + } + } + + public void closeExpiredConnections() { + poolLock.lock(); + try { + idleConnHandler.closeExpiredConnections(); + } finally { + poolLock.unlock(); + } + } + + + //@@@ revise this cleanup stuff (closeIdle+deleteClosed), it's not good + + /** + * Deletes all entries for closed connections. + */ + public abstract void deleteClosedConnections() + ; + + + /** + * Shuts down this pool and all associated resources. + * Overriding methods MUST call the implementation here! + */ + public void shutdown() { + + poolLock.lock(); + try { + + if (isShutDown) + return; + + // no point in monitoring GC anymore + if (refWorker != null) + refWorker.shutdown(); + + // close all connections that are issued to an application + Iterator<BasicPoolEntryRef> iter = issuedConnections.iterator(); + while (iter.hasNext()) { + BasicPoolEntryRef per = iter.next(); + iter.remove(); + BasicPoolEntry entry = per.get(); + if (entry != null) { + closeConnection(entry.getConnection()); + } + } + + // remove all references to connections + //@@@ use this for shutting them down instead? + idleConnHandler.removeAll(); + + isShutDown = true; + + } finally { + poolLock.unlock(); + } + } + + + /** + * Closes a connection from this pool. + * + * @param conn the connection to close, or <code>null</code> + */ + protected void closeConnection(final OperatedClientConnection conn) { + if (conn != null) { + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + + + + + +} // class AbstractConnPool + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java new file mode 100644 index 0000000..dded360 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java $ + * $Revision: 652721 $ + * $Date: 2008-05-01 17:32:20 -0700 (Thu, 01 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.lang.ref.ReferenceQueue; + +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.conn.AbstractPoolEntry; + + + +/** + * Basic implementation of a connection pool entry. + */ +public class BasicPoolEntry extends AbstractPoolEntry { + + /** + * A weak reference to <code>this</code> used to detect GC of entries. + * Pool entries can only be GCed when they are allocated by an application + * and therefore not referenced with a hard link in the manager. + */ + private final BasicPoolEntryRef reference; + + /** + * Creates a new pool entry. + * + * @param op the connection operator + * @param route the planned route for the connection + * @param queue the reference queue for tracking GC of this entry, + * or <code>null</code> + */ + public BasicPoolEntry(ClientConnectionOperator op, + HttpRoute route, + ReferenceQueue<Object> queue) { + super(op, route); + if (route == null) { + throw new IllegalArgumentException("HTTP route may not be null"); + } + this.reference = new BasicPoolEntryRef(this, queue); + } + + protected final OperatedClientConnection getConnection() { + return super.connection; + } + + protected final HttpRoute getPlannedRoute() { + return super.route; + } + + protected final BasicPoolEntryRef getWeakRef() { + return this.reference; + } + + +} // class BasicPoolEntry + + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java new file mode 100644 index 0000000..32df8a5 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java @@ -0,0 +1,80 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; + +import org.apache.http.conn.routing.HttpRoute; + + + +/** + * A weak reference to a {@link BasicPoolEntry BasicPoolEntry}. + * This reference explicitly keeps the planned route, so the connection + * can be reclaimed if it is lost to garbage collection. + */ +public class BasicPoolEntryRef extends WeakReference<BasicPoolEntry> { + + /** The planned route of the entry. */ + private final HttpRoute route; + + + /** + * Creates a new reference to a pool entry. + * + * @param entry the pool entry, must not be <code>null</code> + * @param queue the reference queue, or <code>null</code> + */ + public BasicPoolEntryRef(BasicPoolEntry entry, + ReferenceQueue<Object> queue) { + super(entry, queue); + if (entry == null) { + throw new IllegalArgumentException + ("Pool entry must not be null."); + } + route = entry.getPlannedRoute(); + } + + + /** + * Obtain the planned route for the referenced entry. + * The planned route is still available, even if the entry is gone. + * + * @return the planned route + */ + public final HttpRoute getRoute() { + return this.route; + } + +} // class BasicPoolEntryRef + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java new file mode 100644 index 0000000..29455d0 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java $ + * $Revision: 653214 $ + * $Date: 2008-05-04 07:12:13 -0700 (Sun, 04 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.impl.conn.AbstractPoolEntry; +import org.apache.http.impl.conn.AbstractPooledConnAdapter; + + + +/** + * A connection wrapper and callback handler. + * All connections given out by the manager are wrappers which + * can be {@link #detach detach}ed to prevent further use on release. + */ +public class BasicPooledConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new adapter. + * + * @param tsccm the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected BasicPooledConnAdapter(ThreadSafeClientConnManager tsccm, + AbstractPoolEntry entry) { + super(tsccm, entry); + markReusable(); + } + + + @Override + protected ClientConnectionManager getManager() { + // override needed only to make method visible in this package + return super.getManager(); + } + + + /** + * Obtains the pool entry. + * + * @return the pool entry, or <code>null</code> if detached + */ + protected AbstractPoolEntry getPoolEntry() { + return super.poolEntry; + } + + + // non-javadoc, see base class + @Override + protected void detach() { + // override needed only to make method visible in this package + super.detach(); + } +} diff --git a/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java new file mode 100644 index 0000000..cf59129 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java @@ -0,0 +1,698 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Queue; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.params.ConnPerRoute; +import org.apache.http.conn.params.ConnManagerParams; +import org.apache.http.params.HttpParams; + + +/** + * A connection pool that maintains connections by route. + * This class is derived from <code>MultiThreadedHttpConnectionManager</code> + * in HttpClient 3.x, see there for original authors. It implements the same + * algorithm for connection re-use and connection-per-host enforcement: + * <ul> + * <li>connections are re-used only for the exact same route</li> + * <li>connection limits are enforced per route rather than per host</li> + * </ul> + * Note that access to the pool datastructures is synchronized via the + * {@link AbstractConnPool#poolLock poolLock} in the base class, + * not via <code>synchronized</code> methods. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author and others + */ +public class ConnPoolByRoute extends AbstractConnPool { + + private final Log log = LogFactory.getLog(getClass()); + + /** Connection operator for this pool */ + protected final ClientConnectionOperator operator; + + /** The list of free connections */ + protected Queue<BasicPoolEntry> freeConnections; + + /** The list of WaitingThreads waiting for a connection */ + protected Queue<WaitingThread> waitingThreads; + + /** + * A map of route-specific pools. + * Keys are of class {@link HttpRoute}, + * values of class {@link RouteSpecificPool}. + */ + protected final Map<HttpRoute, RouteSpecificPool> routeToPool; + + protected final int maxTotalConnections; + + private final ConnPerRoute connPerRoute; + + /** + * Creates a new connection pool, managed by route. + */ + public ConnPoolByRoute(final ClientConnectionOperator operator, final HttpParams params) { + super(); + if (operator == null) { + throw new IllegalArgumentException("Connection operator may not be null"); + } + this.operator = operator; + + freeConnections = createFreeConnQueue(); + waitingThreads = createWaitingThreadQueue(); + routeToPool = createRouteToPoolMap(); + maxTotalConnections = ConnManagerParams + .getMaxTotalConnections(params); + connPerRoute = ConnManagerParams + .getMaxConnectionsPerRoute(params); + } + + + /** + * Creates the queue for {@link #freeConnections}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<BasicPoolEntry> createFreeConnQueue() { + return new LinkedList<BasicPoolEntry>(); + } + + /** + * Creates the queue for {@link #waitingThreads}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<WaitingThread> createWaitingThreadQueue() { + return new LinkedList<WaitingThread>(); + } + + /** + * Creates the map for {@link #routeToPool}. + * Called once by the constructor. + * + * @return a map + */ + protected Map<HttpRoute, RouteSpecificPool> createRouteToPoolMap() { + return new HashMap<HttpRoute, RouteSpecificPool>(); + } + + + /** + * Creates a new route-specific pool. + * Called by {@link #getRoutePool} when necessary. + * + * @param route the route + * + * @return the new pool + */ + protected RouteSpecificPool newRouteSpecificPool(HttpRoute route) { + return new RouteSpecificPool(route, connPerRoute.getMaxForRoute(route)); + } + + + /** + * Creates a new waiting thread. + * Called by {@link #getRoutePool} when necessary. + * + * @param cond the condition to wait for + * @param rospl the route specific pool, or <code>null</code> + * + * @return a waiting thread representation + */ + protected WaitingThread newWaitingThread(Condition cond, + RouteSpecificPool rospl) { + return new WaitingThread(cond, rospl); + } + + + /** + * Get a route-specific pool of available connections. + * + * @param route the route + * @param create whether to create the pool if it doesn't exist + * + * @return the pool for the argument route, + * never <code>null</code> if <code>create</code> is <code>true</code> + */ + protected RouteSpecificPool getRoutePool(HttpRoute route, + boolean create) { + RouteSpecificPool rospl = null; + poolLock.lock(); + try { + + rospl = routeToPool.get(route); + if ((rospl == null) && create) { + // no pool for this route yet (or anymore) + rospl = newRouteSpecificPool(route); + routeToPool.put(route, rospl); + } + + } finally { + poolLock.unlock(); + } + + return rospl; + } + + + //@@@ consider alternatives for gathering statistics + public int getConnectionsInPool(HttpRoute route) { + + poolLock.lock(); + try { + // don't allow a pool to be created here! + RouteSpecificPool rospl = getRoutePool(route, false); + return (rospl != null) ? rospl.getEntryCount() : 0; + + } finally { + poolLock.unlock(); + } + } + + @Override + public PoolEntryRequest requestPoolEntry( + final HttpRoute route, + final Object state) { + + final WaitingThreadAborter aborter = new WaitingThreadAborter(); + + return new PoolEntryRequest() { + + public void abortRequest() { + poolLock.lock(); + try { + aborter.abort(); + } finally { + poolLock.unlock(); + } + } + + public BasicPoolEntry getPoolEntry( + long timeout, + TimeUnit tunit) + throws InterruptedException, ConnectionPoolTimeoutException { + return getEntryBlocking(route, state, timeout, tunit, aborter); + } + + }; + } + + /** + * Obtains a pool entry with a connection within the given timeout. + * If a {@link WaitingThread} is used to block, {@link WaitingThreadAborter#setWaitingThread(WaitingThread)} + * must be called before blocking, to allow the thread to be interrupted. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * @param aborter an object which can abort a {@link WaitingThread}. + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + protected BasicPoolEntry getEntryBlocking( + HttpRoute route, Object state, + long timeout, TimeUnit tunit, + WaitingThreadAborter aborter) + throws ConnectionPoolTimeoutException, InterruptedException { + + Date deadline = null; + if (timeout > 0) { + deadline = new Date + (System.currentTimeMillis() + tunit.toMillis(timeout)); + } + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + + RouteSpecificPool rospl = getRoutePool(route, true); + WaitingThread waitingThread = null; + + while (entry == null) { + + if (isShutDown) { + throw new IllegalStateException + ("Connection pool shut down."); + } + + if (log.isDebugEnabled()) { + log.debug("Total connections kept alive: " + freeConnections.size()); + log.debug("Total issued connections: " + issuedConnections.size()); + log.debug("Total allocated connection: " + numConnections + " out of " + maxTotalConnections); + } + + // the cases to check for: + // - have a free connection for that route + // - allowed to create a free connection for that route + // - can delete and replace a free connection for another route + // - need to wait for one of the things above to come true + + entry = getFreeEntry(rospl, state); + if (entry != null) { + break; + } + + boolean hasCapacity = rospl.getCapacity() > 0; + + if (log.isDebugEnabled()) { + log.debug("Available capacity: " + rospl.getCapacity() + + " out of " + rospl.getMaxEntries() + + " [" + route + "][" + state + "]"); + } + + if (hasCapacity && numConnections < maxTotalConnections) { + + entry = createEntry(rospl, operator); + + } else if (hasCapacity && !freeConnections.isEmpty()) { + + deleteLeastUsedEntry(); + entry = createEntry(rospl, operator); + + } else { + + if (log.isDebugEnabled()) { + log.debug("Need to wait for connection" + + " [" + route + "][" + state + "]"); + } + + if (waitingThread == null) { + waitingThread = + newWaitingThread(poolLock.newCondition(), rospl); + aborter.setWaitingThread(waitingThread); + } + + boolean success = false; + try { + rospl.queueThread(waitingThread); + waitingThreads.add(waitingThread); + success = waitingThread.await(deadline); + + } finally { + // In case of 'success', we were woken up by the + // connection pool and should now have a connection + // waiting for us, or else we're shutting down. + // Just continue in the loop, both cases are checked. + rospl.removeThread(waitingThread); + waitingThreads.remove(waitingThread); + } + + // check for spurious wakeup vs. timeout + if (!success && (deadline != null) && + (deadline.getTime() <= System.currentTimeMillis())) { + throw new ConnectionPoolTimeoutException + ("Timeout waiting for connection"); + } + } + } // while no entry + + } finally { + poolLock.unlock(); + } + + return entry; + + } // getEntry + + + // non-javadoc, see base class AbstractConnPool + @Override + public void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) { + + HttpRoute route = entry.getPlannedRoute(); + if (log.isDebugEnabled()) { + log.debug("Freeing connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + if (isShutDown) { + // the pool is shut down, release the + // connection's resources and get out of here + closeConnection(entry.getConnection()); + return; + } + + // no longer issued, we keep a hard reference now + issuedConnections.remove(entry.getWeakRef()); + + RouteSpecificPool rospl = getRoutePool(route, true); + + if (reusable) { + rospl.freeEntry(entry); + freeConnections.add(entry); + idleConnHandler.add(entry.getConnection(), validDuration, timeUnit); + } else { + rospl.dropEntry(); + numConnections--; + } + + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + + } // freeEntry + + + + /** + * If available, get a free pool entry for a route. + * + * @param rospl the route-specific pool from which to get an entry + * + * @return an available pool entry for the given route, or + * <code>null</code> if none is available + */ + protected BasicPoolEntry getFreeEntry(RouteSpecificPool rospl, Object state) { + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + boolean done = false; + while(!done) { + + entry = rospl.allocEntry(state); + + if (entry != null) { + if (log.isDebugEnabled()) { + log.debug("Getting free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + + } + freeConnections.remove(entry); + boolean valid = idleConnHandler.remove(entry.getConnection()); + if(!valid) { + // If the free entry isn't valid anymore, get rid of it + // and loop to find another one that might be valid. + if(log.isDebugEnabled()) + log.debug("Closing expired free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + closeConnection(entry.getConnection()); + // We use dropEntry instead of deleteEntry because the entry + // is no longer "free" (we just allocated it), and deleteEntry + // can only be used to delete free entries. + rospl.dropEntry(); + numConnections--; + } else { + issuedConnections.add(entry.getWeakRef()); + done = true; + } + + } else { + done = true; + if (log.isDebugEnabled()) { + log.debug("No free connections" + + " [" + rospl.getRoute() + "][" + state + "]"); + } + } + } + } finally { + poolLock.unlock(); + } + + return entry; + } + + + /** + * Creates a new pool entry. + * This method assumes that the new connection will be handed + * out immediately. + * + * @param rospl the route-specific pool for which to create the entry + * @param op the operator for creating a connection + * + * @return the new pool entry for a new connection + */ + protected BasicPoolEntry createEntry(RouteSpecificPool rospl, + ClientConnectionOperator op) { + + if (log.isDebugEnabled()) { + log.debug("Creating new connection [" + rospl.getRoute() + "]"); + } + + // the entry will create the connection when needed + BasicPoolEntry entry = + new BasicPoolEntry(op, rospl.getRoute(), refQueue); + + poolLock.lock(); + try { + + rospl.createdEntry(entry); + numConnections++; + + issuedConnections.add(entry.getWeakRef()); + + } finally { + poolLock.unlock(); + } + + return entry; + } + + + /** + * Deletes a given pool entry. + * This closes the pooled connection and removes all references, + * so that it can be GCed. + * + * <p><b>Note:</b> Does not remove the entry from the freeConnections list. + * It is assumed that the caller has already handled this step.</p> + * <!-- @@@ is that a good idea? or rather fix it? --> + * + * @param entry the pool entry for the connection to delete + */ + protected void deleteEntry(BasicPoolEntry entry) { + + HttpRoute route = entry.getPlannedRoute(); + + if (log.isDebugEnabled()) { + log.debug("Deleting connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + + closeConnection(entry.getConnection()); + + RouteSpecificPool rospl = getRoutePool(route, true); + rospl.deleteEntry(entry); + numConnections--; + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + idleConnHandler.remove(entry.getConnection());// not idle, but dead + + } finally { + poolLock.unlock(); + } + } + + + /** + * Delete an old, free pool entry to make room for a new one. + * Used to replace pool entries with ones for a different route. + */ + protected void deleteLeastUsedEntry() { + + try { + poolLock.lock(); + + //@@@ with get() instead of remove, we could + //@@@ leave the removing to deleteEntry() + BasicPoolEntry entry = freeConnections.remove(); + + if (entry != null) { + deleteEntry(entry); + } else if (log.isDebugEnabled()) { + log.debug("No free connection to delete."); + } + + } finally { + poolLock.unlock(); + } + } + + + // non-javadoc, see base class AbstractConnPool + @Override + protected void handleLostEntry(HttpRoute route) { + + poolLock.lock(); + try { + + RouteSpecificPool rospl = getRoutePool(route, true); + rospl.dropEntry(); + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + numConnections--; + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + } + + + /** + * Notifies a waiting thread that a connection is available. + * This will wake a thread waiting in the specific route pool, + * if there is one. + * Otherwise, a thread in the connection pool will be notified. + * + * @param rospl the pool in which to notify, or <code>null</code> + */ + protected void notifyWaitingThread(RouteSpecificPool rospl) { + + //@@@ while this strategy provides for best connection re-use, + //@@@ is it fair? only do this if the connection is open? + // Find the thread we are going to notify. We want to ensure that + // each waiting thread is only interrupted once, so we will remove + // it from all wait queues before interrupting. + WaitingThread waitingThread = null; + + poolLock.lock(); + try { + + if ((rospl != null) && rospl.hasThread()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on pool" + + " [" + rospl.getRoute() + "]"); + } + waitingThread = rospl.nextThread(); + } else if (!waitingThreads.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on any pool"); + } + waitingThread = waitingThreads.remove(); + } else if (log.isDebugEnabled()) { + log.debug("Notifying no-one, there are no waiting threads"); + } + + if (waitingThread != null) { + waitingThread.wakeup(); + } + + } finally { + poolLock.unlock(); + } + } + + + //@@@ revise this cleanup stuff + //@@@ move method to base class when deleteEntry() is fixed + // non-javadoc, see base class AbstractConnPool + @Override + public void deleteClosedConnections() { + + poolLock.lock(); + try { + + Iterator<BasicPoolEntry> iter = freeConnections.iterator(); + while (iter.hasNext()) { + BasicPoolEntry entry = iter.next(); + if (!entry.getConnection().isOpen()) { + iter.remove(); + deleteEntry(entry); + } + } + + } finally { + poolLock.unlock(); + } + } + + + // non-javadoc, see base class AbstractConnPool + @Override + public void shutdown() { + + poolLock.lock(); + try { + + super.shutdown(); + + // close all free connections + //@@@ move this to base class? + Iterator<BasicPoolEntry> ibpe = freeConnections.iterator(); + while (ibpe.hasNext()) { + BasicPoolEntry entry = ibpe.next(); + ibpe.remove(); + closeConnection(entry.getConnection()); + } + + // wake up all waiting threads + Iterator<WaitingThread> iwth = waitingThreads.iterator(); + while (iwth.hasNext()) { + WaitingThread waiter = iwth.next(); + iwth.remove(); + waiter.wakeup(); + } + + routeToPool.clear(); + + } finally { + poolLock.unlock(); + } + } + + +} // class ConnPoolByRoute + diff --git a/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java new file mode 100644 index 0000000..faf5e3b --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java @@ -0,0 +1,68 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.util.concurrent.TimeUnit; + +import org.apache.http.conn.ConnectionPoolTimeoutException; + +/** + * Encapsulates a request for a {@link BasicPoolEntry}. + */ +public interface PoolEntryRequest { + + /** + * Obtains a pool entry with a connection within the given timeout. + * If {@link #abortRequest()} is called before this completes + * an {@link InterruptedException} is thrown. + * + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted or the request was aborted + */ + BasicPoolEntry getPoolEntry( + long timeout, + TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException; + + /** + * Aborts the active or next call to + * {@link #getPoolEntry(long, TimeUnit)}. + */ + void abortRequest(); + +} diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java new file mode 100644 index 0000000..3af28cc --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java @@ -0,0 +1,48 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueHandler.java $ + * $Revision: 603874 $ + * $Date: 2007-12-13 02:42:41 -0800 (Thu, 13 Dec 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.lang.ref.Reference; + + +/** + * Callback handler for {@link RefQueueWorker RefQueueWorker}. + */ +public interface RefQueueHandler { + + /** + * Invoked when a reference is found on the queue. + * + * @param ref the reference to handle + */ + public void handleReference(Reference<?> ref) + ; +} diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java new file mode 100644 index 0000000..9ad5c77 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java @@ -0,0 +1,139 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueWorker.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + + +/** + * A worker thread for processing queued references. + * {@link Reference Reference}s can be + * {@link ReferenceQueue queued} + * automatically by the garbage collector. + * If that feature is used, a daemon thread should be executing + * this worker. It will pick up the queued references and pass them + * on to a handler for appropriate processing. + */ +public class RefQueueWorker implements Runnable { + + private final Log log = LogFactory.getLog(getClass()); + + /** The reference queue to monitor. */ + protected final ReferenceQueue<?> refQueue; + + /** The handler for the references found. */ + protected final RefQueueHandler refHandler; + + + /** + * The thread executing this handler. + * This attribute is also used as a shutdown indicator. + */ + protected volatile Thread workerThread; + + + /** + * Instantiates a new worker to listen for lost connections. + * + * @param queue the queue on which to wait for references + * @param handler the handler to pass the references to + */ + public RefQueueWorker(ReferenceQueue<?> queue, RefQueueHandler handler) { + if (queue == null) { + throw new IllegalArgumentException("Queue must not be null."); + } + if (handler == null) { + throw new IllegalArgumentException("Handler must not be null."); + } + + refQueue = queue; + refHandler = handler; + } + + + /** + * The main loop of this worker. + * If initialization succeeds, this method will only return + * after {@link #shutdown shutdown()}. Only one thread can + * execute the main loop at any time. + */ + public void run() { + + if (this.workerThread == null) { + this.workerThread = Thread.currentThread(); + } + + while (this.workerThread == Thread.currentThread()) { + try { + // remove the next reference and process it + Reference<?> ref = refQueue.remove(); + refHandler.handleReference(ref); + } catch (InterruptedException e) { + //@@@ is logging really necessary? this here is the + //@@@ only reason for having a log in this class + if (log.isDebugEnabled()) { + log.debug(this.toString() + " interrupted", e); + } + } + } + } + + + /** + * Shuts down this worker. + * It can be re-started afterwards by another call to {@link #run run()}. + */ + public void shutdown() { + Thread wt = this.workerThread; + if (wt != null) { + this.workerThread = null; // indicate shutdown + wt.interrupt(); + } + } + + + /** + * Obtains a description of this worker. + * + * @return a descriptive string for this worker + */ + @Override + public String toString() { + return "RefQueueWorker::" + this.workerThread; + } + +} // class RefQueueWorker + diff --git a/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java new file mode 100644 index 0000000..5c63933 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java @@ -0,0 +1,301 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.util.ListIterator; +import java.util.Queue; +import java.util.LinkedList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.util.LangUtils; + + +/** + * A connection sub-pool for a specific route, used by {@link ConnPoolByRoute}. + * The methods in this class are unsynchronized. It is expected that the + * containing pool takes care of synchronization. + */ +public class RouteSpecificPool { + + private final Log log = LogFactory.getLog(getClass()); + + /** The route this pool is for. */ + protected final HttpRoute route; + + /** the maximum number of entries allowed for this pool */ + protected final int maxEntries; + + /** + * The list of free entries. + * This list is managed LIFO, to increase idle times and + * allow for closing connections that are not really needed. + */ + protected final LinkedList<BasicPoolEntry> freeEntries; + + /** The list of threads waiting for this pool. */ + protected final Queue<WaitingThread> waitingThreads; + + /** The number of created entries. */ + protected int numEntries; + + + /** + * Creates a new route-specific pool. + * + * @param route the route for which to pool + * @param maxEntries the maximum number of entries allowed for this pool + */ + public RouteSpecificPool(HttpRoute route, int maxEntries) { + this.route = route; + this.maxEntries = maxEntries; + this.freeEntries = new LinkedList<BasicPoolEntry>(); + this.waitingThreads = new LinkedList<WaitingThread>(); + this.numEntries = 0; + } + + + /** + * Obtains the route for which this pool is specific. + * + * @return the route + */ + public final HttpRoute getRoute() { + return route; + } + + + /** + * Obtains the maximum number of entries allowed for this pool. + * + * @return the max entry number + */ + public final int getMaxEntries() { + return maxEntries; + } + + + /** + * Indicates whether this pool is unused. + * A pool is unused if there is neither an entry nor a waiting thread. + * All entries count, not only the free but also the allocated ones. + * + * @return <code>true</code> if this pool is unused, + * <code>false</code> otherwise + */ + public boolean isUnused() { + return (numEntries < 1) && waitingThreads.isEmpty(); + } + + + /** + * Return remaining capacity of this pool + * + * @return capacity + */ + public int getCapacity() { + return maxEntries - numEntries; + } + + + /** + * Obtains the number of entries. + * This includes not only the free entries, but also those that + * have been created and are currently issued to an application. + * + * @return the number of entries for the route of this pool + */ + public final int getEntryCount() { + return numEntries; + } + + + /** + * Obtains a free entry from this pool, if one is available. + * + * @return an available pool entry, or <code>null</code> if there is none + */ + public BasicPoolEntry allocEntry(final Object state) { + if (!freeEntries.isEmpty()) { + ListIterator<BasicPoolEntry> it = freeEntries.listIterator(freeEntries.size()); + while (it.hasPrevious()) { + BasicPoolEntry entry = it.previous(); + if (LangUtils.equals(state, entry.getState())) { + it.remove(); + return entry; + } + } + } + if (!freeEntries.isEmpty()) { + BasicPoolEntry entry = freeEntries.remove(); + entry.setState(null); + OperatedClientConnection conn = entry.getConnection(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + return entry; + } + return null; + } + + + /** + * Returns an allocated entry to this pool. + * + * @param entry the entry obtained from {@link #allocEntry allocEntry} + * or presented to {@link #createdEntry createdEntry} + */ + public void freeEntry(BasicPoolEntry entry) { + + if (numEntries < 1) { + throw new IllegalStateException + ("No entry created for this pool. " + route); + } + if (numEntries <= freeEntries.size()) { + throw new IllegalStateException + ("No entry allocated from this pool. " + route); + } + freeEntries.add(entry); + } + + + /** + * Indicates creation of an entry for this pool. + * The entry will <i>not</i> be added to the list of free entries, + * it is only recognized as belonging to this pool now. It can then + * be passed to {@link #freeEntry freeEntry}. + * + * @param entry the entry that was created for this pool + */ + public void createdEntry(BasicPoolEntry entry) { + + if (!route.equals(entry.getPlannedRoute())) { + throw new IllegalArgumentException + ("Entry not planned for this pool." + + "\npool: " + route + + "\nplan: " + entry.getPlannedRoute()); + } + + numEntries++; + } + + + /** + * Deletes an entry from this pool. + * Only entries that are currently free in this pool can be deleted. + * Allocated entries can not be deleted. + * + * @param entry the entry to delete from this pool + * + * @return <code>true</code> if the entry was found and deleted, or + * <code>false</code> if the entry was not found + */ + public boolean deleteEntry(BasicPoolEntry entry) { + + final boolean found = freeEntries.remove(entry); + if (found) + numEntries--; + return found; + } + + + /** + * Forgets about an entry from this pool. + * This method is used to indicate that an entry + * {@link #allocEntry allocated} + * from this pool has been lost and will not be returned. + */ + public void dropEntry() { + if (numEntries < 1) { + throw new IllegalStateException + ("There is no entry that could be dropped."); + } + numEntries--; + } + + + /** + * Adds a waiting thread. + * This pool makes no attempt to match waiting threads with pool entries. + * It is the caller's responsibility to check that there is no entry + * before adding a waiting thread. + * + * @param wt the waiting thread + */ + public void queueThread(WaitingThread wt) { + if (wt == null) { + throw new IllegalArgumentException + ("Waiting thread must not be null."); + } + this.waitingThreads.add(wt); + } + + + /** + * Checks whether there is a waiting thread in this pool. + * + * @return <code>true</code> if there is a waiting thread, + * <code>false</code> otherwise + */ + public boolean hasThread() { + return !this.waitingThreads.isEmpty(); + } + + + /** + * Returns the next thread in the queue. + * + * @return a waiting thread, or <code>null</code> if there is none + */ + public WaitingThread nextThread() { + return this.waitingThreads.peek(); + } + + + /** + * Removes a waiting thread, if it is queued. + * + * @param wt the waiting thread + */ + public void removeThread(WaitingThread wt) { + if (wt == null) + return; + + this.waitingThreads.remove(wt); + } + + +} // class RouteSpecificPool diff --git a/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java new file mode 100644 index 0000000..0781e05 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java @@ -0,0 +1,282 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.params.HttpParams; +import org.apache.http.impl.conn.DefaultClientConnectionOperator; + + + +/** + * Manages a pool of {@link OperatedClientConnection client connections}. + * <p> + * This class is derived from <code>MultiThreadedHttpConnectionManager</code> + * in HttpClient 3. See there for original authors. + * </p> + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * @since 4.0 + */ +public class ThreadSafeClientConnManager implements ClientConnectionManager { + + private final Log log = LogFactory.getLog(getClass()); + + /** The schemes supported by this connection manager. */ + protected SchemeRegistry schemeRegistry; + + /** The pool of connections being managed. */ + protected final AbstractConnPool connectionPool; + + /** The operator for opening and updating connections. */ + protected ClientConnectionOperator connOperator; + + + + /** + * Creates a new thread safe connection manager. + * + * @param params the parameters for this manager + * @param schreg the scheme registry, or + * <code>null</code> for the default registry + */ + public ThreadSafeClientConnManager(HttpParams params, + SchemeRegistry schreg) { + + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + this.connectionPool = createConnectionPool(params); + + } // <constructor> + + + @Override + protected void finalize() throws Throwable { + shutdown(); + super.finalize(); + } + + + /** + * Hook for creating the connection pool. + * + * @return the connection pool to use + */ + protected AbstractConnPool createConnectionPool(final HttpParams params) { + + AbstractConnPool acp = new ConnPoolByRoute(connOperator, params); + boolean conngc = true; //@@@ check parameters to decide + if (conngc) { + acp.enableConnectionGC(); + } + return acp; + } + + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry to use, or <code>null</code> + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(SchemeRegistry schreg) { + + return new DefaultClientConnectionOperator(schreg); + } + + + // non-javadoc, see interface ClientConnectionManager + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + + public ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + final PoolEntryRequest poolRequest = connectionPool.requestPoolEntry( + route, state); + + return new ClientConnectionRequest() { + + public void abortRequest() { + poolRequest.abortRequest(); + } + + public ManagedClientConnection getConnection( + long timeout, TimeUnit tunit) throws InterruptedException, + ConnectionPoolTimeoutException { + if (route == null) { + throw new IllegalArgumentException("Route may not be null."); + } + + if (log.isDebugEnabled()) { + log.debug("ThreadSafeClientConnManager.getConnection: " + + route + ", timeout = " + timeout); + } + + BasicPoolEntry entry = poolRequest.getPoolEntry(timeout, tunit); + return new BasicPooledConnAdapter(ThreadSafeClientConnManager.this, entry); + } + + }; + + } + + + // non-javadoc, see interface ClientConnectionManager + public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { + + if (!(conn instanceof BasicPooledConnAdapter)) { + throw new IllegalArgumentException + ("Connection class mismatch, " + + "connection not obtained from this manager."); + } + BasicPooledConnAdapter hca = (BasicPooledConnAdapter) conn; + if ((hca.getPoolEntry() != null) && (hca.getManager() != this)) { + throw new IllegalArgumentException + ("Connection not obtained from this manager."); + } + + try { + // make sure that the response has been read completely + if (hca.isOpen() && !hca.isMarkedReusable()) { + if (log.isDebugEnabled()) { + log.debug + ("Released connection open but not marked reusable."); + } + // In MTHCM, there would be a call to + // SimpleHttpConnectionManager.finishLastResponse(conn); + // Consuming the response is handled outside in 4.0. + + // make sure this connection will not be re-used + // Shut down rather than close, we might have gotten here + // because of a shutdown trigger. + // Shutdown of the adapter also clears the tracked route. + hca.shutdown(); + } + } catch (IOException iox) { + //@@@ log as warning? let pass? + if (log.isDebugEnabled()) + log.debug("Exception shutting down released connection.", + iox); + } finally { + BasicPoolEntry entry = (BasicPoolEntry) hca.getPoolEntry(); + boolean reusable = hca.isMarkedReusable(); + hca.detach(); + if (entry != null) { + connectionPool.freeEntry(entry, reusable, validDuration, timeUnit); + } + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void shutdown() { + connectionPool.shutdown(); + } + + + /** + * Gets the total number of pooled connections for the given route. + * This is the total number of connections that have been created and + * are still in use by this connection manager for the route. + * This value will not exceed the maximum number of connections per host. + * + * @param route the route in question + * + * @return the total number of pooled connections for that route + */ + public int getConnectionsInPool(HttpRoute route) { + return ((ConnPoolByRoute)connectionPool).getConnectionsInPool( + route); + } + + + /** + * Gets the total number of pooled connections. This is the total number of + * connections that have been created and are still in use by this connection + * manager. This value will not exceed the maximum number of connections + * in total. + * + * @return the total number of pooled connections + */ + public int getConnectionsInPool() { + synchronized (connectionPool) { + return connectionPool.numConnections; //@@@ + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void closeIdleConnections(long idleTimeout, TimeUnit tunit) { + // combine these two in a single call? + connectionPool.closeIdleConnections(idleTimeout, tunit); + connectionPool.deleteClosedConnections(); + } + + public void closeExpiredConnections() { + connectionPool.closeExpiredConnections(); + connectionPool.deleteClosedConnections(); + } + + +} // class ThreadSafeClientConnManager + diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThread.java b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java new file mode 100644 index 0000000..a50e11f --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java @@ -0,0 +1,197 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java $ + * $Revision: 649217 $ + * $Date: 2008-04-17 11:32:32 -0700 (Thu, 17 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.util.Date; +import java.util.concurrent.locks.Condition; + + +/** + * Represents a thread waiting for a connection. + * This class implements throwaway objects. It is instantiated whenever + * a thread needs to wait. Instances are not re-used, except if the + * waiting thread experiences a spurious wakeup and continues to wait. + * <br/> + * All methods assume external synchronization on the condition + * passed to the constructor. + * Instances of this class do <i>not</i> synchronize access! + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + */ +public class WaitingThread { + + /** The condition on which the thread is waiting. */ + private final Condition cond; + + /** The route specific pool on which the thread is waiting. */ + //@@@ replace with generic pool interface + private final RouteSpecificPool pool; + + /** The thread that is waiting for an entry. */ + private Thread waiter; + + /** True if this was interrupted. */ + private boolean aborted; + + + /** + * Creates a new entry for a waiting thread. + * + * @param cond the condition for which to wait + * @param pool the pool on which the thread will be waiting, + * or <code>null</code> + */ + public WaitingThread(Condition cond, RouteSpecificPool pool) { + + if (cond == null) { + throw new IllegalArgumentException("Condition must not be null."); + } + + this.cond = cond; + this.pool = pool; + } + + + /** + * Obtains the condition. + * + * @return the condition on which to wait, never <code>null</code> + */ + public final Condition getCondition() { + // not synchronized + return this.cond; + } + + + /** + * Obtains the pool, if there is one. + * + * @return the pool on which a thread is or was waiting, + * or <code>null</code> + */ + public final RouteSpecificPool getPool() { + // not synchronized + return this.pool; + } + + + /** + * Obtains the thread, if there is one. + * + * @return the thread which is waiting, or <code>null</code> + */ + public final Thread getThread() { + // not synchronized + return this.waiter; + } + + + /** + * Blocks the calling thread. + * This method returns when the thread is notified or interrupted, + * if a timeout occurrs, or if there is a spurious wakeup. + * <br/> + * This method assumes external synchronization. + * + * @param deadline when to time out, or <code>null</code> for no timeout + * + * @return <code>true</code> if the condition was satisfied, + * <code>false</code> in case of a timeout. + * Typically, a call to {@link #wakeup} is used to indicate + * that the condition was satisfied. Since the condition is + * accessible outside, this cannot be guaranteed though. + * + * @throws InterruptedException if the waiting thread was interrupted + * + * @see #wakeup + */ + public boolean await(Date deadline) + throws InterruptedException { + + // This is only a sanity check. We cannot synchronize here, + // the lock would not be released on calling cond.await() below. + if (this.waiter != null) { + throw new IllegalStateException + ("A thread is already waiting on this object." + + "\ncaller: " + Thread.currentThread() + + "\nwaiter: " + this.waiter); + } + + if (aborted) + throw new InterruptedException("Operation interrupted"); + + this.waiter = Thread.currentThread(); + + boolean success = false; + try { + if (deadline != null) { + success = this.cond.awaitUntil(deadline); + } else { + this.cond.await(); + success = true; + } + if (aborted) + throw new InterruptedException("Operation interrupted"); + } finally { + this.waiter = null; + } + return success; + + } // await + + + /** + * Wakes up the waiting thread. + * <br/> + * This method assumes external synchronization. + */ + public void wakeup() { + + // If external synchronization and pooling works properly, + // this cannot happen. Just a sanity check. + if (this.waiter == null) { + throw new IllegalStateException + ("Nobody waiting on this object."); + } + + // One condition might be shared by several WaitingThread instances. + // It probably isn't, but just in case: wake all, not just one. + this.cond.signalAll(); + } + + public void interrupt() { + aborted = true; + this.cond.signalAll(); + } + + +} // class WaitingThread diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java new file mode 100644 index 0000000..1844457 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java @@ -0,0 +1,62 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java $ + * $Revision: 649220 $ + * $Date: 2008-04-17 11:40:24 -0700 (Thu, 17 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +/** A simple class that can interrupt a {@link WaitingThread}. */ +public class WaitingThreadAborter { + + private WaitingThread waitingThread; + private boolean aborted; + + /** + * If a waiting thread has been set, interrupts it. + */ + public void abort() { + aborted = true; + + if (waitingThread != null) + waitingThread.interrupt(); + + } + + /** + * Sets the waiting thread. If this has already been aborted, + * the waiting thread is immediately interrupted. + * + * @param waitingThread The thread to interrupt when aborting. + */ + public void setWaitingThread(WaitingThread waitingThread) { + this.waitingThread = waitingThread; + if (aborted) + waitingThread.interrupt(); + } + +} diff --git a/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png Binary files differnew file mode 100644 index 0000000..2e2820d --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png diff --git a/src/org/apache/http/impl/conn/tsccm/package.html b/src/org/apache/http/impl/conn/tsccm/package.html new file mode 100644 index 0000000..5aca5d4 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/package.html @@ -0,0 +1,205 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/package.html $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> + +The implementation of a thread-safe client connection manager. + +<center> +<img src="doc-files/tsccm-structure.png" alt="Relation Diagram"/> +</center> + +<p> +The implementation is structured into three areas, as illustrated +by the diagram above. +Facing the application is the <i>Manager</i> (green), which internally +maintains a <i>Pool</i> (yellow) of connections and waiting threads. +Both Manager and Pool rely on <i>Operations</i> (cyan) to provide the +actual connections. +</p> +<p> +In order to allow connection garbage collection, it is +imperative that hard object references between the areas are +restricted to the relations indicated by arrows in the diagram: +</p> +<ul> +<li>Applications reference only the Manager objects.</li> +<li>Manager objects reference Pool objects, but not vice versa.</li> +<li>Operations objects do not reference either Manager or Pool objects.</li> +</ul> + +<p> +The following table shows a selection of classes and interfaces, +and their assignment to the three areas. +</p> +<center> +<table border="1"> +<colgroup> + <col width="50%"/> + <col width="50%"/> +</colgroup> + +<tr> +<td style="text-align: center; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager} +</td> +<td style="text-align: center; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.AbstractConnPool} +</td> +</tr> + +<tr> +<td style="text-align: center; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPooledConnAdapter} +</td> +<td style="text-align: center; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute} +</td> +</tr> + +<!-- appears on both sides! --> + +<tr> +<td style="text-align: right; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +</td> +<td style="text-align: left; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +</td> +</tr> + +<!-- ====================== --> + +<tr style="border-width: 5px;"> +</tr> + +<tr> +<td colspan="2" style="text-align: center; background-color: #00ffff;"> +{@link org.apache.http.conn.ClientConnectionOperator} +</td> +</tr> + +<tr> +<td colspan="2" style="text-align: center; background-color: #00ffff;"> +{@link org.apache.http.conn.OperatedClientConnection} +</td> +</tr> + +</table> +</center> + +<p> +The Manager area has implementations for the connection management +interfaces {@link org.apache.http.conn.ClientConnectionManager} +and {@link org.apache.http.conn.ManagedClientConnection}. +The latter is an adapter from managed to operated connections, based on a +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}. +<br/> +The Pool area shows an abstract pool class +{@link org.apache.http.impl.conn.tsccm.AbstractConnPool} +and a concrete implementation +{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute} +which uses the same basic algorithm as the +<code>MultiThreadedHttpConnectionManager</code> +in HttpClient 3.x. +A pool contains instances of +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}. +Most other classes in this package also belong to the Pool area. +<br/> +In the Operations area, you will find only the interfaces for +operated connections as defined in the org.apache.http.conn package. +The connection manager will work with all correct implementations +of these interfaces. This package therefore does not define anything +specific to the Operations area. +</p> + +<p> +As you have surely noticed, the +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +appears in both the Manager and Pool areas. +This is where things get tricky for connection garbage collection. +<br/> +A connection pool may start a background thread to implement cleanup. +In that case, the connection pool will not be garbage collected until +it is shut down, since the background thread keeps a hard reference +to the pool. The pool itself keeps hard references to the pooled entries, +which in turn reference idle connections. Neither of these is subject +to garbage collection. +Only the shutdown of the pool will stop the background thread, +thereby enabling garbage collection of the pool objects. +<br/> +A pool entry that is passed to an application by means of a connection +adapter will move from the Pool area to the Manager area. When the +connection is released by the application, the manager returns the +entry back to the pool. With that step, the pool entry moves from +the Manager area back to the Pool area. +While the entry is in the Manager area, the pool MUST NOT keep a +hard reference to it. +</p> + +<p> +The purpose of connection garbage collection is to detect when an +application fails to return a connection. In order to achieve this, +the only hard reference to the pool entry in the Manager area is +in the connection wrapper. The manager will not keep a hard reference +to the connection wrapper either, since that wrapper is effectively +moving to the Application area. +If the application drops it's reference to the connection wrapper, +that wrapper will be garbage collected, and with it the pool entry. +<br/> +In order to detect garbage collection of pool entries handed out +to the application, the pool keeps a <i>weak reference</i> to the +entry. Instances of +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntryRef} +combine the weak reference with information about the route for +which the pool entry was allocated. If one of these entry references +becomes stale, the pool can accommodate for the lost connection. +This is triggered either by a background thread waiting for the +references to be queued by the garbage collector, or by the +application calling a {@link + org.apache.http.conn.ClientConnectionManager#closeIdleConnections cleanup} +method of the connection manager. +<br/> +Basically the same trick is used for detecting garbage collection +of the connection manager itself. The pool keeps a weak reference +to the connection manager that created it. However, this will work +only if there is a background thread to detect when that reference +is queued by the garbage collector. Otherwise, a finalizer of the +connection manager will shut down the pool and release it's resources. +</p> + + +</body> +</html> diff --git a/src/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java b/src/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java new file mode 100644 index 0000000..1aa4d2c --- /dev/null +++ b/src/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java $ + * $Revision: 503525 $ + * $Date: 2007-02-04 17:15:08 -0800 (Sun, 04 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; + +public abstract class AbstractCookieAttributeHandler implements CookieAttributeHandler { + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + // Do nothing + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + // Always match + return true; + } + +} diff --git a/src/org/apache/http/impl/cookie/AbstractCookieSpec.java b/src/org/apache/http/impl/cookie/AbstractCookieSpec.java new file mode 100644 index 0000000..3e47a4d --- /dev/null +++ b/src/org/apache/http/impl/cookie/AbstractCookieSpec.java @@ -0,0 +1,110 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/AbstractCookieSpec.java $ + * $Revision: 617207 $ + * $Date: 2008-01-31 12:14:12 -0800 (Thu, 31 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieSpec; + +/** + * Abstract cookie specification which can delegate the job of parsing, + * validation or matching cookie attributes to a number of arbitrary + * {@link CookieAttributeHandler}s. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public abstract class AbstractCookieSpec implements CookieSpec { + + /** + * Stores attribute name -> attribute handler mappings + */ + private final Map<String, CookieAttributeHandler> attribHandlerMap; + + /** + * Default constructor + * */ + public AbstractCookieSpec() { + super(); + this.attribHandlerMap = new HashMap<String, CookieAttributeHandler>(10); + } + + public void registerAttribHandler( + final String name, final CookieAttributeHandler handler) { + if (name == null) { + throw new IllegalArgumentException("Attribute name may not be null"); + } + if (handler == null) { + throw new IllegalArgumentException("Attribute handler may not be null"); + } + this.attribHandlerMap.put(name, handler); + } + + /** + * Finds an attribute handler {@link CookieAttributeHandler} for the + * given attribute. Returns <tt>null</tt> if no attribute handler is + * found for the specified attribute. + * + * @param name attribute name. e.g. Domain, Path, etc. + * @return an attribute handler or <tt>null</tt> + */ + protected CookieAttributeHandler findAttribHandler(final String name) { + return this.attribHandlerMap.get(name); + } + + /** + * Gets attribute handler {@link CookieAttributeHandler} for the + * given attribute. + * + * @param name attribute name. e.g. Domain, Path, etc. + * @throws IllegalStateException if handler not found for the + * specified attribute. + */ + protected CookieAttributeHandler getAttribHandler(final String name) { + CookieAttributeHandler handler = findAttribHandler(name); + if (handler == null) { + throw new IllegalStateException("Handler not registered for " + + name + " attribute."); + } else { + return handler; + } + } + + protected Collection<CookieAttributeHandler> getAttribHandlers() { + return this.attribHandlerMap.values(); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicClientCookie.java b/src/org/apache/http/impl/cookie/BasicClientCookie.java new file mode 100644 index 0000000..6ec6c2b --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicClientCookie.java @@ -0,0 +1,376 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie.java $ + * $Revision: 659191 $ + * $Date: 2008-05-22 11:26:53 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.SetCookie; + +/** + * HTTP "magic-cookie" represents a piece of state information + * that the HTTP agent and the target server can exchange to maintain + * a session. + * + * @author B.C. Holmes + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a> + * @author Rod Waldhoff + * @author dIon Gillard + * @author Sean C. Sullivan + * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a> + * @author Marc A. Saegesser + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @version $Revision: 659191 $ + */ +public class BasicClientCookie implements SetCookie, ClientCookie, Cloneable { + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicClientCookie(final String name, final String value) { + super(); + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + this.name = name; + this.attribs = new HashMap<String, String>(); + this.value = value; + } + + /** + * Returns the name. + * + * @return String name The name + */ + public String getName() { + return this.name; + } + + /** + * Returns the value. + * + * @return String value The current value. + */ + public String getValue() { + return this.value; + } + + /** + * Sets the value + * + * @param value + */ + public void setValue(final String value) { + this.value = value; + } + + /** + * Returns the comment describing the purpose of this cookie, or + * <tt>null</tt> if no such comment has been defined. + * + * @return comment + * + * @see #setComment(String) + */ + public String getComment() { + return cookieComment; + } + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described using this comment. + * + * @param comment + * + * @see #getComment() + */ + public void setComment(String comment) { + cookieComment = comment; + } + + + /** + * Returns null. Cookies prior to RFC2965 do not set this attribute + */ + public String getCommentURL() { + return null; + } + + + /** + * Returns the expiration {@link Date} of the cookie, or <tt>null</tt> + * if none exists. + * <p><strong>Note:</strong> the object returned by this method is + * considered immutable. Changing it (e.g. using setTime()) could result + * in undefined behaviour. Do so at your peril. </p> + * @return Expiration {@link Date}, or <tt>null</tt>. + * + * @see #setExpiryDate(java.util.Date) + * + */ + public Date getExpiryDate() { + return cookieExpiryDate; + } + + /** + * Sets expiration date. + * <p><strong>Note:</strong> the object returned by this method is considered + * immutable. Changing it (e.g. using setTime()) could result in undefined + * behaviour. Do so at your peril.</p> + * + * @param expiryDate the {@link Date} after which this cookie is no longer valid. + * + * @see #getExpiryDate + * + */ + public void setExpiryDate (Date expiryDate) { + cookieExpiryDate = expiryDate; + } + + + /** + * Returns <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise. + * + * @return <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise + */ + public boolean isPersistent() { + return (null != cookieExpiryDate); + } + + + /** + * Returns domain attribute of the cookie. + * + * @return the value of the domain attribute + * + * @see #setDomain(java.lang.String) + */ + public String getDomain() { + return cookieDomain; + } + + /** + * Sets the domain attribute. + * + * @param domain The value of the domain attribute + * + * @see #getDomain + */ + public void setDomain(String domain) { + if (domain != null) { + cookieDomain = domain.toLowerCase(Locale.ENGLISH); + } else { + cookieDomain = null; + } + } + + + /** + * Returns the path attribute of the cookie + * + * @return The value of the path attribute. + * + * @see #setPath(java.lang.String) + */ + public String getPath() { + return cookiePath; + } + + /** + * Sets the path attribute. + * + * @param path The value of the path attribute + * + * @see #getPath + * + */ + public void setPath(String path) { + cookiePath = path; + } + + /** + * @return <code>true</code> if this cookie should only be sent over secure connections. + * @see #setSecure(boolean) + */ + public boolean isSecure() { + return isSecure; + } + + /** + * Sets the secure attribute of the cookie. + * <p> + * When <tt>true</tt> the cookie should only be sent + * using a secure protocol (https). This should only be set when + * the cookie's originating server used a secure protocol to set the + * cookie's value. + * + * @param secure The value of the secure attribute + * + * @see #isSecure() + */ + public void setSecure (boolean secure) { + isSecure = secure; + } + + + /** + * Returns null. Cookies prior to RFC2965 do not set this attribute + */ + public int[] getPorts() { + return null; + } + + + /** + * Returns the version of the cookie specification to which this + * cookie conforms. + * + * @return the version of the cookie. + * + * @see #setVersion(int) + * + */ + public int getVersion() { + return cookieVersion; + } + + /** + * Sets the version of the cookie specification to which this + * cookie conforms. + * + * @param version the version of the cookie. + * + * @see #getVersion + */ + public void setVersion(int version) { + cookieVersion = version; + } + + /** + * Returns true if this cookie has expired. + * @param date Current time + * + * @return <tt>true</tt> if the cookie has expired. + */ + public boolean isExpired(final Date date) { + if (date == null) { + throw new IllegalArgumentException("Date may not be null"); + } + return (cookieExpiryDate != null + && cookieExpiryDate.getTime() <= date.getTime()); + } + + public void setAttribute(final String name, final String value) { + this.attribs.put(name, value); + } + + public String getAttribute(final String name) { + return this.attribs.get(name); + } + + public boolean containsAttribute(final String name) { + return this.attribs.get(name) != null; + } + + @Override + public Object clone() throws CloneNotSupportedException { + BasicClientCookie clone = (BasicClientCookie) super.clone(); + clone.attribs = new HashMap<String, String>(this.attribs); + return clone; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("[version: "); + buffer.append(Integer.toString(this.cookieVersion)); + buffer.append("]"); + buffer.append("[name: "); + buffer.append(this.name); + buffer.append("]"); + buffer.append("[value: "); + buffer.append(this.value); + buffer.append("]"); + buffer.append("[domain: "); + buffer.append(this.cookieDomain); + buffer.append("]"); + buffer.append("[path: "); + buffer.append(this.cookiePath); + buffer.append("]"); + buffer.append("[expiry: "); + buffer.append(this.cookieExpiryDate); + buffer.append("]"); + return buffer.toString(); + } + + // ----------------------------------------------------- Instance Variables + + /** Cookie name */ + private final String name; + + /** Cookie attributes as specified by the origin server */ + private Map<String, String> attribs; + + /** Cookie value */ + private String value; + + /** Comment attribute. */ + private String cookieComment; + + /** Domain attribute. */ + private String cookieDomain; + + /** Expiration {@link Date}. */ + private Date cookieExpiryDate; + + /** Path attribute. */ + private String cookiePath; + + /** My secure flag. */ + private boolean isSecure; + + /** The version of the cookie specification I was created from. */ + private int cookieVersion; + +} + diff --git a/src/org/apache/http/impl/cookie/BasicClientCookie2.java b/src/org/apache/http/impl/cookie/BasicClientCookie2.java new file mode 100644 index 0000000..86ec60d --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicClientCookie2.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java $ + * $Revision: 659191 $ + * $Date: 2008-05-22 11:26:53 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Date; + +import org.apache.http.cookie.SetCookie2; + +/** + * HTTP "magic-cookie" represents a piece of state information + * that the HTTP agent and the target server can exchange to maintain + * a session as specified by RFC2965. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class BasicClientCookie2 extends BasicClientCookie implements SetCookie2 { + + private String commentURL; + private int[] ports; + private boolean discard; + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicClientCookie2(final String name, final String value) { + super(name, value); + } + + @Override + public int[] getPorts() { + return this.ports; + } + + public void setPorts(final int[] ports) { + this.ports = ports; + } + + @Override + public String getCommentURL() { + return this.commentURL; + } + + public void setCommentURL(final String commentURL) { + this.commentURL = commentURL; + } + + public void setDiscard(boolean discard) { + this.discard = discard; + } + + @Override + public boolean isPersistent() { + return !this.discard && super.isPersistent(); + } + + @Override + public boolean isExpired(final Date date) { + return this.discard || super.isExpired(date); + } + + @Override + public Object clone() throws CloneNotSupportedException { + BasicClientCookie2 clone = (BasicClientCookie2) super.clone(); + clone.ports = this.ports.clone(); + return clone; + } + +} + diff --git a/src/org/apache/http/impl/cookie/BasicCommentHandler.java b/src/org/apache/http/impl/cookie/BasicCommentHandler.java new file mode 100644 index 0000000..ce8baea --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicCommentHandler.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicCommentHandler.java $ + * $Revision: 558519 $ + * $Date: 2007-07-22 11:19:49 -0700 (Sun, 22 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicCommentHandler extends AbstractCookieAttributeHandler { + + public BasicCommentHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + cookie.setComment(value); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicDomainHandler.java b/src/org/apache/http/impl/cookie/BasicDomainHandler.java new file mode 100644 index 0000000..267faf8 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicDomainHandler.java @@ -0,0 +1,122 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicDomainHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicDomainHandler implements CookieAttributeHandler { + + public BasicDomainHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for domain attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for domain attribute"); + } + cookie.setDomain(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + // Validate the cookies domain attribute. NOTE: Domains without + // any dots are allowed to support hosts on private LANs that don't + // have DNS names. Since they have no dots, to domain-match the + // request-host and domain must be identical for the cookie to sent + // back to the origin-server. + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + throw new MalformedCookieException("Cookie domain may not be null"); + } + if (host.contains(".")) { + // Not required to have at least two dots. RFC 2965. + // A Set-Cookie2 with Domain=ajax.com will be accepted. + + // domain must match host + if (!host.endsWith(domain)) { + if (domain.startsWith(".")) { + domain = domain.substring(1, domain.length()); + } + if (!host.equals(domain)) { + throw new MalformedCookieException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + } + } else { + if (!host.equals(domain)) { + throw new MalformedCookieException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + if (host.equals(domain)) { + return true; + } + if (!domain.startsWith(".")) { + domain = '.' + domain; + } + return host.endsWith(domain) || host.equals(domain.substring(1)); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicExpiresHandler.java b/src/org/apache/http/impl/cookie/BasicExpiresHandler.java new file mode 100644 index 0000000..a53519e --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicExpiresHandler.java @@ -0,0 +1,65 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicExpiresHandler.java $ + * $Revision: 558519 $ + * $Date: 2007-07-22 11:19:49 -0700 (Sun, 22 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + + +public class BasicExpiresHandler extends AbstractCookieAttributeHandler { + + /** Valid date patterns */ + private final String[] datepatterns; + + public BasicExpiresHandler(final String[] datepatterns) { + if (datepatterns == null) { + throw new IllegalArgumentException("Array of date patterns may not be null"); + } + this.datepatterns = datepatterns; + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for expires attribute"); + } + try { + cookie.setExpiryDate(DateUtils.parseDate(value, this.datepatterns)); + } catch (DateParseException dpe) { + throw new MalformedCookieException("Unable to parse expires attribute: " + + value); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicMaxAgeHandler.java b/src/org/apache/http/impl/cookie/BasicMaxAgeHandler.java new file mode 100644 index 0000000..92a5c7d --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicMaxAgeHandler.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicMaxAgeHandler.java $ + * $Revision: 581953 $ + * $Date: 2007-10-04 08:53:50 -0700 (Thu, 04 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import java.util.Date; + +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicMaxAgeHandler extends AbstractCookieAttributeHandler { + + public BasicMaxAgeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for max-age attribute"); + } + int age; + try { + age = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new MalformedCookieException ("Invalid max-age attribute: " + + value); + } + if (age < 0) { + throw new MalformedCookieException ("Negative max-age attribute: " + + value); + } + cookie.setExpiryDate(new Date(System.currentTimeMillis() + age * 1000L)); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicPathHandler.java b/src/org/apache/http/impl/cookie/BasicPathHandler.java new file mode 100644 index 0000000..43a12c8 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicPathHandler.java @@ -0,0 +1,91 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicPathHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicPathHandler implements CookieAttributeHandler { + + public BasicPathHandler() { + super(); + } + + public void parse(final SetCookie cookie, String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null || value.trim().length() == 0) { + value = "/"; + } + cookie.setPath(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (!match(cookie, origin)) { + throw new MalformedCookieException( + "Illegal path attribute \"" + cookie.getPath() + + "\". Path of origin: \"" + origin.getPath() + "\""); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String targetpath = origin.getPath(); + String topmostPath = cookie.getPath(); + if (topmostPath == null) { + topmostPath = "/"; + } + if (topmostPath.length() > 1 && topmostPath.endsWith("/")) { + topmostPath = topmostPath.substring(0, topmostPath.length() - 1); + } + boolean match = targetpath.startsWith (topmostPath); + // if there is a match and these values are not exactly the same we have + // to make sure we're not matcing "/foobar" and "/foo" + if (match && targetpath.length() != topmostPath.length()) { + if (!topmostPath.endsWith("/")) { + match = (targetpath.charAt(topmostPath.length()) == '/'); + } + } + return match; + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicSecureHandler.java b/src/org/apache/http/impl/cookie/BasicSecureHandler.java new file mode 100644 index 0000000..9100b9c --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicSecureHandler.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicSecureHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicSecureHandler extends AbstractCookieAttributeHandler { + + public BasicSecureHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + cookie.setSecure(true); + } + + @Override + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + return !cookie.isSecure() || origin.isSecure(); + } + +} diff --git a/src/org/apache/http/impl/cookie/BestMatchSpec.java b/src/org/apache/http/impl/cookie/BestMatchSpec.java new file mode 100644 index 0000000..e33fec3 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BestMatchSpec.java @@ -0,0 +1,186 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java $ + * $Revision: 657334 $ + * $Date: 2008-05-17 04:44:16 -0700 (Sat, 17 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.List; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.MalformedCookieException; + +/** + * 'Meta' cookie specification that selects a cookie policy depending + * on the format of the cookie(s) + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BestMatchSpec implements CookieSpec { + + private final String[] datepatterns; + private final boolean oneHeader; + + private RFC2965Spec strict; + private BrowserCompatSpec compat; + private NetscapeDraftSpec netscape; + + public BestMatchSpec(final String[] datepatterns, boolean oneHeader) { + super(); + this.datepatterns = datepatterns; + this.oneHeader = oneHeader; + } + + public BestMatchSpec() { + this(null, false); + } + + private RFC2965Spec getStrict() { + if (this.strict == null) { + this.strict = new RFC2965Spec(this.datepatterns, this.oneHeader); + } + return strict; + } + + private BrowserCompatSpec getCompat() { + if (this.compat == null) { + this.compat = new BrowserCompatSpec(this.datepatterns); + } + return compat; + } + + private NetscapeDraftSpec getNetscape() { + if (this.netscape == null) { + String[] patterns = this.datepatterns; + if (patterns == null) { + patterns = BrowserCompatSpec.DATE_PATTERNS; + } + this.netscape = new NetscapeDraftSpec(patterns); + } + return netscape; + } + + public List<Cookie> parse( + final Header header, + final CookieOrigin origin) throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + HeaderElement[] helems = header.getElements(); + boolean versioned = false; + boolean netscape = false; + for (HeaderElement helem: helems) { + if (helem.getParameterByName("version") != null) { + versioned = true; + } + if (helem.getParameterByName("expires") != null) { + netscape = true; + } + } + if (netscape) { + + } + // Do we have a cookie with a version attribute? + if (versioned) { + return getStrict().parse(helems, origin); + } else if (netscape) { + // Need to parse the header again, + // because Netscape draft cannot handle + // comma separators + return getNetscape().parse(header, origin); + } else { + return getCompat().parse(helems, origin); + } + } + + public void validate( + final Cookie cookie, + final CookieOrigin origin) throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + if (cookie.getVersion() > 0) { + getStrict().validate(cookie, origin); + } else { + getCompat().validate(cookie, origin); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + if (cookie.getVersion() > 0) { + return getStrict().match(cookie, origin); + } else { + return getCompat().match(cookie, origin); + } + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookie may not be null"); + } + int version = Integer.MAX_VALUE; + for (Cookie cookie: cookies) { + if (cookie.getVersion() < version) { + version = cookie.getVersion(); + } + } + if (version > 0) { + return getStrict().formatCookies(cookies); + } else { + return getCompat().formatCookies(cookies); + } + } + + public int getVersion() { + return getStrict().getVersion(); + } + + public Header getVersionHeader() { + return getStrict().getVersionHeader(); + } + +}
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/BestMatchSpecFactory.java b/src/org/apache/http/impl/cookie/BestMatchSpecFactory.java new file mode 100644 index 0000000..cb632bb --- /dev/null +++ b/src/org/apache/http/impl/cookie/BestMatchSpecFactory.java @@ -0,0 +1,57 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BestMatchSpecFactory.java $ + * $Revision: 613707 $ + * $Date: 2008-01-20 16:28:37 -0800 (Sun, 20 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BestMatchSpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new BestMatchSpec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS), + params.getBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, false)); + } else { + return new BestMatchSpec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/BrowserCompatSpec.java b/src/org/apache/http/impl/cookie/BrowserCompatSpec.java new file mode 100644 index 0000000..d7bc0da --- /dev/null +++ b/src/org/apache/http/impl/cookie/BrowserCompatSpec.java @@ -0,0 +1,188 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java $ + * $Revision: 657334 $ + * $Date: 2008-05-17 04:44:16 -0700 (Sat, 17 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +/** + * Cookie specification that strives to closely mimic (mis)behavior of + * common web browser applications such as Microsoft Internet Explorer + * and Mozilla FireFox. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BrowserCompatSpec extends CookieSpecBase { + + /** Valid date patterns used per default */ + protected static final String[] DATE_PATTERNS = new String[] { + DateUtils.PATTERN_RFC1123, + DateUtils.PATTERN_RFC1036, + DateUtils.PATTERN_ASCTIME, + "EEE, dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MMM-yyyy HH-mm-ss z", + "EEE, dd MMM yy HH:mm:ss z", + "EEE dd-MMM-yyyy HH:mm:ss z", + "EEE dd MMM yyyy HH:mm:ss z", + "EEE dd-MMM-yyyy HH-mm-ss z", + "EEE dd-MMM-yy HH:mm:ss z", + "EEE dd MMM yy HH:mm:ss z", + "EEE,dd-MMM-yy HH:mm:ss z", + "EEE,dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MM-yyyy HH:mm:ss z", + }; + + private final String[] datepatterns; + + /** Default constructor */ + public BrowserCompatSpec(final String[] datepatterns) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = DATE_PATTERNS; + } + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new BasicDomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public BrowserCompatSpec() { + this(null); + } + + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String headervalue = header.getValue(); + boolean isNetscapeCookie = false; + int i1 = headervalue.toLowerCase(Locale.ENGLISH).indexOf("expires="); + if (i1 != -1) { + i1 += "expires=".length(); + int i2 = headervalue.indexOf(';', i1); + if (i2 == -1) { + i2 = headervalue.length(); + } + try { + DateUtils.parseDate(headervalue.substring(i1, i2), this.datepatterns); + isNetscapeCookie = true; + } catch (DateParseException e) { + // Does not look like a valid expiry date + } + } + HeaderElement[] elems = null; + if (isNetscapeCookie) { + NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT; + CharArrayBuffer buffer; + ParserCursor cursor; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + cursor = new ParserCursor( + ((FormattedHeader) header).getValuePos(), + buffer.length()); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedCookieException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + cursor = new ParserCursor(0, buffer.length()); + } + elems = new HeaderElement[] { parser.parseHeader(buffer, cursor) }; + } else { + elems = header.getElements(); + } + return parse(elems, origin); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookies may not be null"); + } + if (cookies.isEmpty()) { + throw new IllegalArgumentException("List of cookies may not be empty"); + } + CharArrayBuffer buffer = new CharArrayBuffer(20 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + for (int i = 0; i < cookies.size(); i++) { + Cookie cookie = cookies.get(i); + if (i > 0) { + buffer.append("; "); + } + buffer.append(cookie.getName()); + buffer.append("="); + String s = cookie.getValue(); + if (s != null) { + buffer.append(s); + } + } + List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + public int getVersion() { + return 0; + } + + public Header getVersionHeader() { + return null; + } + +} diff --git a/src/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java b/src/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java new file mode 100644 index 0000000..71c0c05 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java @@ -0,0 +1,56 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java $ + * $Revision: 576068 $ + * $Date: 2007-09-16 03:25:01 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BrowserCompatSpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new BrowserCompatSpec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS)); + } else { + return new BrowserCompatSpec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/CookieSpecBase.java b/src/org/apache/http/impl/cookie/CookieSpecBase.java new file mode 100644 index 0000000..8e70bb1 --- /dev/null +++ b/src/org/apache/http/impl/cookie/CookieSpecBase.java @@ -0,0 +1,131 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/CookieSpecBase.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; + +/** + * Cookie management functions shared by all specification. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public abstract class CookieSpecBase extends AbstractCookieSpec { + + protected static String getDefaultPath(final CookieOrigin origin) { + String defaultPath = origin.getPath(); + int lastSlashIndex = defaultPath.lastIndexOf('/'); + if (lastSlashIndex >= 0) { + if (lastSlashIndex == 0) { + //Do not remove the very first slash + lastSlashIndex = 1; + } + defaultPath = defaultPath.substring(0, lastSlashIndex); + } + return defaultPath; + } + + protected static String getDefaultDomain(final CookieOrigin origin) { + return origin.getHost(); + } + + protected List<Cookie> parse(final HeaderElement[] elems, final CookieOrigin origin) + throws MalformedCookieException { + List<Cookie> cookies = new ArrayList<Cookie>(elems.length); + for (HeaderElement headerelement : elems) { + String name = headerelement.getName(); + String value = headerelement.getValue(); + if (name == null || name.length() == 0) { + throw new MalformedCookieException("Cookie name may not be empty"); + } + + BasicClientCookie cookie = new BasicClientCookie(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + + // cycle through the parameters + NameValuePair[] attribs = headerelement.getParameters(); + for (int j = attribs.length - 1; j >= 0; j--) { + NameValuePair attrib = attribs[j]; + String s = attrib.getName().toLowerCase(Locale.ENGLISH); + + cookie.setAttribute(s, attrib.getValue()); + + CookieAttributeHandler handler = findAttribHandler(s); + if (handler != null) { + handler.parse(cookie, attrib.getValue()); + } + } + cookies.add(cookie); + } + return cookies; + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + for (CookieAttributeHandler handler: getAttribHandlers()) { + handler.validate(cookie, origin); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + for (CookieAttributeHandler handler: getAttribHandlers()) { + if (!handler.match(cookie, origin)) { + return false; + } + } + return true; + } + +} diff --git a/src/org/apache/http/impl/cookie/DateParseException.java b/src/org/apache/http/impl/cookie/DateParseException.java new file mode 100644 index 0000000..c80b669 --- /dev/null +++ b/src/org/apache/http/impl/cookie/DateParseException.java @@ -0,0 +1,60 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/DateParseException.java $ + * $Revision: 609105 $ + * $Date: 2008-01-05 00:55:00 -0800 (Sat, 05 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + + +/** + * An exception to indicate an error parsing a date string. + * + * @see DateUtils + * + * @author Michael Becke + */ +public class DateParseException extends Exception { + + private static final long serialVersionUID = 4417696455000643370L; + + /** + * + */ + public DateParseException() { + super(); + } + + /** + * @param message the exception message + */ + public DateParseException(String message) { + super(message); + } + +} diff --git a/src/org/apache/http/impl/cookie/DateUtils.java b/src/org/apache/http/impl/cookie/DateUtils.java new file mode 100644 index 0000000..a0a056c --- /dev/null +++ b/src/org/apache/http/impl/cookie/DateUtils.java @@ -0,0 +1,261 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/DateUtils.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.lang.ref.SoftReference; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +/** + * A utility class for parsing and formatting HTTP dates as used in cookies and + * other headers. This class handles dates as defined by RFC 2616 section + * 3.3.1 as well as some other common non-standard formats. + * + * @author Christopher Brown + * @author Michael Becke + */ +public final class DateUtils { + + /** + * Date format pattern used to parse HTTP date headers in RFC 1123 format. + */ + public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in RFC 1036 format. + */ + public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in ANSI C + * <code>asctime()</code> format. + */ + public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; + + private static final String[] DEFAULT_PATTERNS = new String[] { + PATTERN_RFC1036, + PATTERN_RFC1123, + PATTERN_ASCTIME + }; + + private static final Date DEFAULT_TWO_DIGIT_YEAR_START; + + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + static { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(GMT); + calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); + } + + /** + * Parses a date value. The formats used for parsing the date value are retrieved from + * the default http params. + * + * @param dateValue the date value to parse + * + * @return the parsed date + * + * @throws DateParseException if the value could not be parsed using any of the + * supported date formats + */ + public static Date parseDate(String dateValue) throws DateParseException { + return parseDate(dateValue, null, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * + * @return the parsed date + * + * @throws DateParseException if none of the dataFormats could parse the dateValue + */ + public static Date parseDate(final String dateValue, String[] dateFormats) + throws DateParseException { + return parseDate(dateValue, dateFormats, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * @param startDate During parsing, two digit years will be placed in the range + * <code>startDate</code> to <code>startDate + 100 years</code>. This value may + * be <code>null</code>. When <code>null</code> is given as a parameter, year + * <code>2000</code> will be used. + * + * @return the parsed date + * + * @throws DateParseException if none of the dataFormats could parse the dateValue + */ + public static Date parseDate( + String dateValue, + String[] dateFormats, + Date startDate + ) throws DateParseException { + + if (dateValue == null) { + throw new IllegalArgumentException("dateValue is null"); + } + if (dateFormats == null) { + dateFormats = DEFAULT_PATTERNS; + } + if (startDate == null) { + startDate = DEFAULT_TWO_DIGIT_YEAR_START; + } + // trim single quotes around date if present + // see issue #5279 + if (dateValue.length() > 1 + && dateValue.startsWith("'") + && dateValue.endsWith("'") + ) { + dateValue = dateValue.substring (1, dateValue.length() - 1); + } + + for (String dateFormat : dateFormats) { + SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); + dateParser.set2DigitYearStart(startDate); + + try { + return dateParser.parse(dateValue); + } catch (ParseException pe) { + // ignore this exception, we will try the next format + } + } + + // we were unable to parse the date + throw new DateParseException("Unable to parse the date " + dateValue); + } + + /** + * Formats the given date according to the RFC 1123 pattern. + * + * @param date The date to format. + * @return An RFC 1123 formatted date string. + * + * @see #PATTERN_RFC1123 + */ + public static String formatDate(Date date) { + return formatDate(date, PATTERN_RFC1123); + } + + /** + * Formats the given date according to the specified pattern. The pattern + * must conform to that used by the {@link SimpleDateFormat simple date + * format} class. + * + * @param date The date to format. + * @param pattern The pattern to use for formatting the date. + * @return A formatted date string. + * + * @throws IllegalArgumentException If the given date pattern is invalid. + * + * @see SimpleDateFormat + */ + public static String formatDate(Date date, String pattern) { + if (date == null) throw new IllegalArgumentException("date is null"); + if (pattern == null) throw new IllegalArgumentException("pattern is null"); + + SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern); + return formatter.format(date); + } + + /** This class should not be instantiated. */ + private DateUtils() { + } + + /** + * A factory for {@link SimpleDateFormat}s. The instances are stored in a + * threadlocal way because SimpleDateFormat is not threadsafe as noted in + * {@link SimpleDateFormat its javadoc}. + * + * @author Daniel Mueller + */ + final static class DateFormatHolder { + + private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> + THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() { + + @Override + protected SoftReference<Map<String, SimpleDateFormat>> initialValue() { + return new SoftReference<Map<String, SimpleDateFormat>>( + new HashMap<String, SimpleDateFormat>()); + } + + }; + + /** + * creates a {@link SimpleDateFormat} for the requested format string. + * + * @param pattern + * a non-<code>null</code> format String according to + * {@link SimpleDateFormat}. The format is not checked against + * <code>null</code> since all paths go through + * {@link DateUtils}. + * @return the requested format. This simple dateformat should not be used + * to {@link SimpleDateFormat#applyPattern(String) apply} to a + * different pattern. + */ + public static SimpleDateFormat formatFor(String pattern) { + SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get(); + Map<String, SimpleDateFormat> formats = ref.get(); + if (formats == null) { + formats = new HashMap<String, SimpleDateFormat>(); + THREADLOCAL_FORMATS.set( + new SoftReference<Map<String, SimpleDateFormat>>(formats)); + } + + SimpleDateFormat format = formats.get(pattern); + if (format == null) { + format = new SimpleDateFormat(pattern, Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + formats.put(pattern, format); + } + + return format; + } + + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDomainHandler.java b/src/org/apache/http/impl/cookie/NetscapeDomainHandler.java new file mode 100644 index 0000000..8b785ae --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDomainHandler.java @@ -0,0 +1,106 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDomainHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import java.util.Locale; +import java.util.StringTokenizer; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; + +public class NetscapeDomainHandler extends BasicDomainHandler { + + public NetscapeDomainHandler() { + super(); + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + super.validate(cookie, origin); + // Perform Netscape Cookie draft specific validation + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (host.contains(".")) { + int domainParts = new StringTokenizer(domain, ".").countTokens(); + + if (isSpecialDomain(domain)) { + if (domainParts < 2) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates the Netscape cookie specification for " + + "special domains"); + } + } else { + if (domainParts < 3) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates the Netscape cookie specification"); + } + } + } + } + + /** + * Checks if the given domain is in one of the seven special + * top level domains defined by the Netscape cookie specification. + * @param domain The domain. + * @return True if the specified domain is "special" + */ + private static boolean isSpecialDomain(final String domain) { + final String ucDomain = domain.toUpperCase(Locale.ENGLISH); + return ucDomain.endsWith(".COM") + || ucDomain.endsWith(".EDU") + || ucDomain.endsWith(".NET") + || ucDomain.endsWith(".GOV") + || ucDomain.endsWith(".MIL") + || ucDomain.endsWith(".ORG") + || ucDomain.endsWith(".INT"); + } + + @Override +public boolean match(Cookie cookie, CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + return host.endsWith(domain); + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java b/src/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java new file mode 100644 index 0000000..ca6b7fa --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java @@ -0,0 +1,78 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java $ + * $Revision: 603563 $ + * $Date: 2007-12-12 03:17:55 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.message.BasicHeaderElement; +import org.apache.http.message.BasicHeaderValueParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +public class NetscapeDraftHeaderParser { + + public final static NetscapeDraftHeaderParser DEFAULT = new NetscapeDraftHeaderParser(); + + private final static char[] DELIMITERS = new char[] { ';' }; + + private final BasicHeaderValueParser nvpParser; + + public NetscapeDraftHeaderParser() { + super(); + this.nvpParser = BasicHeaderValueParser.DEFAULT; + } + + public HeaderElement parseHeader( + final CharArrayBuffer buffer, + final ParserCursor cursor) throws ParseException { + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + NameValuePair nvp = this.nvpParser.parseNameValuePair(buffer, cursor, DELIMITERS); + List<NameValuePair> params = new ArrayList<NameValuePair>(); + while (!cursor.atEnd()) { + NameValuePair param = this.nvpParser.parseNameValuePair(buffer, cursor, DELIMITERS); + params.add(param); + } + return new BasicHeaderElement( + nvp.getName(), + nvp.getValue(), params.toArray(new NameValuePair[params.size()])); + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDraftSpec.java b/src/org/apache/http/impl/cookie/NetscapeDraftSpec.java new file mode 100644 index 0000000..3bc4f9f --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDraftSpec.java @@ -0,0 +1,182 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpec.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +/** + * Netscape cookie draft compliant cookie policy + * + * @author B.C. Holmes + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a> + * @author Rod Waldhoff + * @author dIon Gillard + * @author Sean C. Sullivan + * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a> + * @author Marc A. Saegesser + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @since 4.0 + */ +public class NetscapeDraftSpec extends CookieSpecBase { + + protected static final String EXPIRES_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss z"; + + private final String[] datepatterns; + + /** Default constructor */ + public NetscapeDraftSpec(final String[] datepatterns) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = new String[] { EXPIRES_PATTERN }; + } + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new NetscapeDomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public NetscapeDraftSpec() { + this(null); + } + + /** + * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s. + * + * <p>Syntax of the Set-Cookie HTTP Response Header:</p> + * + * <p>This is the format a CGI script would use to add to + * the HTTP headers a new piece of data which is to be stored by + * the client for later retrieval.</p> + * + * <PRE> + * Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure + * </PRE> + * + * <p>Please note that Netscape draft specification does not fully + * conform to the HTTP header format. Netscape draft does not specify + * whether multiple cookies may be sent in one header. Hence, comma + * character may be present in unquoted cookie value or unquoted + * parameter value.</p> + * + * @see <a href="http://wp.netscape.com/newsref/std/cookie_spec.html"> + * The Cookie Spec.</a> + * + * @param header the <tt>Set-Cookie</tt> received from the server + * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value + * @throws MalformedCookieException if an exception occurs during parsing + */ + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT; + CharArrayBuffer buffer; + ParserCursor cursor; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + cursor = new ParserCursor( + ((FormattedHeader) header).getValuePos(), + buffer.length()); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedCookieException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + cursor = new ParserCursor(0, buffer.length()); + } + return parse(new HeaderElement[] { parser.parseHeader(buffer, cursor) }, origin); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookies may not be null"); + } + if (cookies.isEmpty()) { + throw new IllegalArgumentException("List of cookies may not be empty"); + } + CharArrayBuffer buffer = new CharArrayBuffer(20 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + for (int i = 0; i < cookies.size(); i++) { + Cookie cookie = cookies.get(i); + if (i > 0) { + buffer.append("; "); + } + buffer.append(cookie.getName()); + String s = cookie.getValue(); + if (s != null) { + buffer.append("="); + buffer.append(s); + } + } + List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + public int getVersion() { + return 0; + } + + public Header getVersionHeader() { + return null; + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java b/src/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java new file mode 100644 index 0000000..0dcb187 --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java @@ -0,0 +1,56 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java $ + * $Revision: 657334 $ + * $Date: 2008-05-17 04:44:16 -0700 (Sat, 17 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class NetscapeDraftSpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new NetscapeDraftSpec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS)); + } else { + return new NetscapeDraftSpec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109DomainHandler.java b/src/org/apache/http/impl/cookie/RFC2109DomainHandler.java new file mode 100644 index 0000000..9cfd484 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109DomainHandler.java @@ -0,0 +1,126 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109DomainHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import java.util.Locale; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class RFC2109DomainHandler implements CookieAttributeHandler { + + public RFC2109DomainHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for domain attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for domain attribute"); + } + cookie.setDomain(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + throw new MalformedCookieException("Cookie domain may not be null"); + } + if (!domain.equals(host)) { + int dotIndex = domain.indexOf('.'); + if (dotIndex == -1) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" does not match the host \"" + + host + "\""); + } + // domain must start with dot + if (!domain.startsWith(".")) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates RFC 2109: domain must start with a dot"); + } + // domain must have at least one embedded dot + dotIndex = domain.indexOf('.', 1); + if (dotIndex < 0 || dotIndex == domain.length() - 1) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates RFC 2109: domain must contain an embedded dot"); + } + host = host.toLowerCase(Locale.ENGLISH); + if (!host.endsWith(domain)) { + throw new MalformedCookieException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + // host minus domain may not contain any dots + String hostWithoutDomain = host.substring(0, host.length() - domain.length()); + if (hostWithoutDomain.indexOf('.') != -1) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates RFC 2109: host minus domain may not contain any dots"); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + return host.equals(domain) || (domain.startsWith(".") && host.endsWith(domain)); + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109Spec.java b/src/org/apache/http/impl/cookie/RFC2109Spec.java new file mode 100644 index 0000000..9e45408 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109Spec.java @@ -0,0 +1,246 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.CookiePathComparator; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; + +/** + * RFC 2109 compliant cookie policy + * + * @author B.C. Holmes + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a> + * @author Rod Waldhoff + * @author dIon Gillard + * @author Sean C. Sullivan + * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a> + * @author Marc A. Saegesser + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @since 4.0 + */ + +public class RFC2109Spec extends CookieSpecBase { + + private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator(); + + private final static String[] DATE_PATTERNS = { + DateUtils.PATTERN_RFC1123, + DateUtils.PATTERN_RFC1036, + DateUtils.PATTERN_ASCTIME + }; + + private final String[] datepatterns; + private final boolean oneHeader; + + /** Default constructor */ + public RFC2109Spec(final String[] datepatterns, boolean oneHeader) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = DATE_PATTERNS; + } + this.oneHeader = oneHeader; + registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2109VersionHandler()); + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2109DomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public RFC2109Spec() { + this(null, false); + } + + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + HeaderElement[] elems = header.getElements(); + return parse(elems, origin); + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + String name = cookie.getName(); + if (name.indexOf(' ') != -1) { + throw new MalformedCookieException("Cookie name may not contain blanks"); + } + if (name.startsWith("$")) { + throw new MalformedCookieException("Cookie name may not start with $"); + } + super.validate(cookie, origin); + } + + public List<Header> formatCookies(List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookies may not be null"); + } + if (cookies.isEmpty()) { + throw new IllegalArgumentException("List of cookies may not be empty"); + } + if (cookies.size() > 1) { + // Create a mutable copy and sort the copy. + cookies = new ArrayList<Cookie>(cookies); + Collections.sort(cookies, PATH_COMPARATOR); + } + if (this.oneHeader) { + return doFormatOneHeader(cookies); + } else { + return doFormatManyHeaders(cookies); + } + } + + private List<Header> doFormatOneHeader(final List<Cookie> cookies) { + int version = Integer.MAX_VALUE; + // Pick the lowest common denominator + for (Cookie cookie : cookies) { + if (cookie.getVersion() < version) { + version = cookie.getVersion(); + } + } + CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + buffer.append("$Version="); + buffer.append(Integer.toString(version)); + for (Cookie cooky : cookies) { + buffer.append("; "); + Cookie cookie = cooky; + formatCookieAsVer(buffer, cookie, version); + } + List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + private List<Header> doFormatManyHeaders(final List<Cookie> cookies) { + List<Header> headers = new ArrayList<Header>(cookies.size()); + for (Cookie cookie : cookies) { + int version = cookie.getVersion(); + CharArrayBuffer buffer = new CharArrayBuffer(40); + buffer.append("Cookie: "); + buffer.append("$Version="); + buffer.append(Integer.toString(version)); + buffer.append("; "); + formatCookieAsVer(buffer, cookie, version); + headers.add(new BufferedHeader(buffer)); + } + return headers; + } + + /** + * Return a name/value string suitable for sending in a <tt>"Cookie"</tt> + * header as defined in RFC 2109 for backward compatibility with cookie + * version 0 + * @param buffer The char array buffer to use for output + * @param name The cookie name + * @param value The cookie value + * @param version The cookie version + */ + protected void formatParamAsVer(final CharArrayBuffer buffer, + final String name, final String value, int version) { + buffer.append(name); + buffer.append("="); + if (value != null) { + if (version > 0) { + buffer.append('\"'); + buffer.append(value); + buffer.append('\"'); + } else { + buffer.append(value); + } + } + } + + /** + * Return a string suitable for sending in a <tt>"Cookie"</tt> header + * as defined in RFC 2109 for backward compatibility with cookie version 0 + * @param buffer The char array buffer to use for output + * @param cookie The {@link Cookie} to be formatted as string + * @param version The version to use. + */ + protected void formatCookieAsVer(final CharArrayBuffer buffer, + final Cookie cookie, int version) { + formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version); + if (cookie.getPath() != null) { + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) { + buffer.append("; "); + formatParamAsVer(buffer, "$Path", cookie.getPath(), version); + } + } + if (cookie.getDomain() != null) { + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { + buffer.append("; "); + formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version); + } + } + } + + public int getVersion() { + return 1; + } + + public Header getVersionHeader() { + return null; + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109SpecFactory.java b/src/org/apache/http/impl/cookie/RFC2109SpecFactory.java new file mode 100644 index 0000000..35c506e --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109SpecFactory.java @@ -0,0 +1,57 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109SpecFactory.java $ + * $Revision: 576068 $ + * $Date: 2007-09-16 03:25:01 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class RFC2109SpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new RFC2109Spec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS), + params.getBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, false)); + } else { + return new RFC2109Spec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109VersionHandler.java b/src/org/apache/http/impl/cookie/RFC2109VersionHandler.java new file mode 100644 index 0000000..d2c4955 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109VersionHandler.java @@ -0,0 +1,74 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109VersionHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class RFC2109VersionHandler extends AbstractCookieAttributeHandler { + + public RFC2109VersionHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for version attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for version attribute"); + } + try { + cookie.setVersion(Integer.parseInt(value)); + } catch (NumberFormatException e) { + throw new MalformedCookieException("Invalid version: " + + e.getMessage()); + } + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (cookie.getVersion() < 0) { + throw new MalformedCookieException("Cookie version may not be negative"); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java new file mode 100644 index 0000000..aa3a1c5 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"CommantURL"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ + public class RFC2965CommentUrlAttributeHandler implements CookieAttributeHandler { + + public RFC2965CommentUrlAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) cookie; + cookie2.setCommentURL(commenturl); + } + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + + }
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java new file mode 100644 index 0000000..aa81145 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"Discard"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ + public class RFC2965DiscardAttributeHandler implements CookieAttributeHandler { + + public RFC2965DiscardAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) cookie; + cookie2.setDiscard(true); + } + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + + }
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java new file mode 100644 index 0000000..b07e5e9 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java @@ -0,0 +1,195 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Locale; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +/** + * <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec. + * + * @author jain.samit@gmail.com (Samit Jain) + * + * @since 3.1 + */ +public class RFC2965DomainAttributeHandler implements CookieAttributeHandler { + + public RFC2965DomainAttributeHandler() { + super(); + } + + /** + * Parse cookie domain attribute. + */ + public void parse(final SetCookie cookie, String domain) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (domain == null) { + throw new MalformedCookieException( + "Missing value for domain attribute"); + } + if (domain.trim().length() == 0) { + throw new MalformedCookieException( + "Blank value for domain attribute"); + } + domain = domain.toLowerCase(Locale.ENGLISH); + if (!domain.startsWith(".")) { + // Per RFC 2965 section 3.2.2 + // "... If an explicitly specified value does not start with + // a dot, the user agent supplies a leading dot ..." + // That effectively implies that the domain attribute + // MAY NOT be an IP address of a host name + domain = '.' + domain; + } + cookie.setDomain(domain); + } + + /** + * Performs domain-match as defined by the RFC2965. + * <p> + * Host A's name domain-matches host B's if + * <ol> + * <ul>their host name strings string-compare equal; or</ul> + * <ul>A is a HDN string and has the form NB, where N is a non-empty + * name string, B has the form .B', and B' is a HDN string. (So, + * x.y.com domain-matches .Y.com but not Y.com.)</ul> + * </ol> + * + * @param host host name where cookie is received from or being sent to. + * @param domain The cookie domain attribute. + * @return true if the specified host matches the given domain. + */ + public boolean domainMatch(String host, String domain) { + boolean match = host.equals(domain) + || (domain.startsWith(".") && host.endsWith(domain)); + + return match; + } + + /** + * Validate cookie domain attribute. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost().toLowerCase(Locale.ENGLISH); + if (cookie.getDomain() == null) { + throw new MalformedCookieException("Invalid cookie state: " + + "domain not specified"); + } + String cookieDomain = cookie.getDomain().toLowerCase(Locale.ENGLISH); + + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { + // Domain attribute must start with a dot + if (!cookieDomain.startsWith(".")) { + throw new MalformedCookieException("Domain attribute \"" + + cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot"); + } + + // Domain attribute must contain at least one embedded dot, + // or the value must be equal to .local. + int dotIndex = cookieDomain.indexOf('.', 1); + if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1)) + && (!cookieDomain.equals(".local"))) { + throw new MalformedCookieException( + "Domain attribute \"" + cookie.getDomain() + + "\" violates RFC 2965: the value contains no embedded dots " + + "and the value is not .local"); + } + + // The effective host name must domain-match domain attribute. + if (!domainMatch(host, cookieDomain)) { + throw new MalformedCookieException( + "Domain attribute \"" + cookie.getDomain() + + "\" violates RFC 2965: effective host name does not " + + "domain-match domain attribute."); + } + + // effective host name minus domain must not contain any dots + String effectiveHostWithoutDomain = host.substring( + 0, host.length() - cookieDomain.length()); + if (effectiveHostWithoutDomain.indexOf('.') != -1) { + throw new MalformedCookieException("Domain attribute \"" + + cookie.getDomain() + "\" violates RFC 2965: " + + "effective host minus domain may not contain any dots"); + } + } else { + // Domain was not specified in header. In this case, domain must + // string match request host (case-insensitive). + if (!cookie.getDomain().equals(host)) { + throw new MalformedCookieException("Illegal domain attribute: \"" + + cookie.getDomain() + "\"." + + "Domain of origin: \"" + + host + "\""); + } + } + } + + /** + * Match cookie domain attribute. + */ + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost().toLowerCase(Locale.ENGLISH); + String cookieDomain = cookie.getDomain(); + + // The effective host name MUST domain-match the Domain + // attribute of the cookie. + if (!domainMatch(host, cookieDomain)) { + return false; + } + // effective host name minus domain must not contain any dots + String effectiveHostWithoutDomain = host.substring( + 0, host.length() - cookieDomain.length()); + return effectiveHostWithoutDomain.indexOf('.') == -1; + } + +}
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java new file mode 100644 index 0000000..b881cda --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java @@ -0,0 +1,168 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.StringTokenizer; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"Port"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ +public class RFC2965PortAttributeHandler implements CookieAttributeHandler { + + public RFC2965PortAttributeHandler() { + super(); + } + + /** + * Parses the given Port attribute value (e.g. "8000,8001,8002") + * into an array of ports. + * + * @param portValue port attribute value + * @return parsed array of ports + * @throws MalformedCookieException if there is a problem in + * parsing due to invalid portValue. + */ + private static int[] parsePortAttribute(final String portValue) + throws MalformedCookieException { + StringTokenizer st = new StringTokenizer(portValue, ","); + int[] ports = new int[st.countTokens()]; + try { + int i = 0; + while(st.hasMoreTokens()) { + ports[i] = Integer.parseInt(st.nextToken().trim()); + if (ports[i] < 0) { + throw new MalformedCookieException ("Invalid Port attribute."); + } + ++i; + } + } catch (NumberFormatException e) { + throw new MalformedCookieException ("Invalid Port " + + "attribute: " + e.getMessage()); + } + return ports; + } + + /** + * Returns <tt>true</tt> if the given port exists in the given + * ports list. + * + * @param port port of host where cookie was received from or being sent to. + * @param ports port list + * @return true returns <tt>true</tt> if the given port exists in + * the given ports list; <tt>false</tt> otherwise. + */ + private static boolean portMatch(int port, int[] ports) { + boolean portInList = false; + for (int i = 0, len = ports.length; i < len; i++) { + if (port == ports[i]) { + portInList = true; + break; + } + } + return portInList; + } + + /** + * Parse cookie port attribute. + */ + public void parse(final SetCookie cookie, final String portValue) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) cookie; + if (portValue != null && portValue.trim().length() > 0) { + int[] ports = parsePortAttribute(portValue); + cookie2.setPorts(ports); + } + } + } + + /** + * Validate cookie port attribute. If the Port attribute was specified + * in header, the request port must be in cookie's port list. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (!portMatch(port, cookie.getPorts())) { + throw new MalformedCookieException( + "Port attribute violates RFC 2965: " + + "Request port not found in cookie's port list."); + } + } + } + + /** + * Match cookie port attribute. If the Port attribute is not specified + * in header, the cookie can be sent to any port. Otherwise, the request port + * must be in the cookie's port list. + */ + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (cookie.getPorts() == null) { + // Invalid cookie state: port not specified + return false; + } + if (!portMatch(port, cookie.getPorts())) { + return false; + } + } + return true; + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2965Spec.java b/src/org/apache/http/impl/cookie/RFC2965Spec.java new file mode 100644 index 0000000..9422fdf --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965Spec.java @@ -0,0 +1,259 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965Spec.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; + +/** + * <p>RFC 2965 specific cookie management functions.</p> + * + * @author jain.samit@gmail.com (Samit Jain) + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 3.1 + */ +public class RFC2965Spec extends RFC2109Spec { + + /** + * Default constructor + * + */ + public RFC2965Spec() { + this(null, false); + } + + public RFC2965Spec(final String[] datepatterns, boolean oneHeader) { + super(datepatterns, oneHeader); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2965DomainAttributeHandler()); + registerAttribHandler(ClientCookie.PORT_ATTR, new RFC2965PortAttributeHandler()); + registerAttribHandler(ClientCookie.COMMENTURL_ATTR, new RFC2965CommentUrlAttributeHandler()); + registerAttribHandler(ClientCookie.DISCARD_ATTR, new RFC2965DiscardAttributeHandler()); + registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2965VersionAttributeHandler()); + } + + private BasicClientCookie createCookie( + final String name, final String value, final CookieOrigin origin) { + BasicClientCookie cookie = new BasicClientCookie(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + return cookie; + } + + private BasicClientCookie createCookie2( + final String name, final String value, final CookieOrigin origin) { + BasicClientCookie2 cookie = new BasicClientCookie2(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + cookie.setPorts(new int [] { origin.getPort() }); + return cookie; + } + + @Override + public List<Cookie> parse( + final Header header, + CookieOrigin origin) throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + + origin = adjustEffectiveHost(origin); + + HeaderElement[] elems = header.getElements(); + + List<Cookie> cookies = new ArrayList<Cookie>(elems.length); + for (HeaderElement headerelement : elems) { + String name = headerelement.getName(); + String value = headerelement.getValue(); + if (name == null || name.length() == 0) { + throw new MalformedCookieException("Cookie name may not be empty"); + } + + BasicClientCookie cookie; + if (header.getName().equals(SM.SET_COOKIE2)) { + cookie = createCookie2(name, value, origin); + } else { + cookie = createCookie(name, value, origin); + } + + // cycle through the parameters + NameValuePair[] attribs = headerelement.getParameters(); + + // Eliminate duplicate attributes. The first occurrence takes precedence + // See RFC2965: 3.2 Origin Server Role + Map<String, NameValuePair> attribmap = + new HashMap<String, NameValuePair>(attribs.length); + for (int j = attribs.length - 1; j >= 0; j--) { + NameValuePair param = attribs[j]; + attribmap.put(param.getName().toLowerCase(Locale.ENGLISH), param); + } + for (Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) { + NameValuePair attrib = entry.getValue(); + String s = attrib.getName().toLowerCase(Locale.ENGLISH); + + cookie.setAttribute(s, attrib.getValue()); + + CookieAttributeHandler handler = findAttribHandler(s); + if (handler != null) { + handler.parse(cookie, attrib.getValue()); + } + } + cookies.add(cookie); + } + return cookies; + } + + @Override + public void validate(final Cookie cookie, CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + origin = adjustEffectiveHost(origin); + super.validate(cookie, origin); + } + + @Override + public boolean match(final Cookie cookie, CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + origin = adjustEffectiveHost(origin); + return super.match(cookie, origin); + } + + /** + * Adds valid Port attribute value, e.g. "8000,8001,8002" + */ + @Override + protected void formatCookieAsVer(final CharArrayBuffer buffer, + final Cookie cookie, int version) { + super.formatCookieAsVer(buffer, cookie, version); + // format port attribute + if (cookie instanceof ClientCookie) { + // Test if the port attribute as set by the origin server is not blank + String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR); + if (s != null) { + buffer.append("; $Port"); + buffer.append("=\""); + if (s.trim().length() > 0) { + int[] ports = cookie.getPorts(); + if (ports != null) { + for (int i = 0, len = ports.length; i < len; i++) { + if (i > 0) { + buffer.append(","); + } + buffer.append(Integer.toString(ports[i])); + } + } + } + buffer.append("\""); + } + } + } + + /** + * Set 'effective host name' as defined in RFC 2965. + * <p> + * If a host name contains no dots, the effective host name is + * that name with the string .local appended to it. Otherwise + * the effective host name is the same as the host name. Note + * that all effective host names contain at least one dot. + * + * @param origin origin where cookie is received from or being sent to. + * @return + */ + private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) { + String host = origin.getHost(); + + // Test if the host name appears to be a fully qualified DNS name, + // IPv4 address or IPv6 address + boolean isLocalHost = true; + for (int i = 0; i < host.length(); i++) { + char ch = host.charAt(i); + if (ch == '.' || ch == ':') { + isLocalHost = false; + break; + } + } + if (isLocalHost) { + host += ".local"; + return new CookieOrigin( + host, + origin.getPort(), + origin.getPath(), + origin.isSecure()); + } else { + return origin; + } + } + + @Override + public int getVersion() { + return 1; + } + + @Override + public Header getVersionHeader() { + CharArrayBuffer buffer = new CharArrayBuffer(40); + buffer.append(SM.COOKIE2); + buffer.append(": "); + buffer.append("$Version="); + buffer.append(Integer.toString(getVersion())); + return new BufferedHeader(buffer); + } + +} + diff --git a/src/org/apache/http/impl/cookie/RFC2965SpecFactory.java b/src/org/apache/http/impl/cookie/RFC2965SpecFactory.java new file mode 100644 index 0000000..4b3cc4d --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965SpecFactory.java @@ -0,0 +1,57 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965SpecFactory.java $ + * $Revision: 581953 $ + * $Date: 2007-10-04 08:53:50 -0700 (Thu, 04 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class RFC2965SpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new RFC2965Spec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS), + params.getBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, false)); + } else { + return new RFC2965Spec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java new file mode 100644 index 0000000..8ea8481 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java @@ -0,0 +1,96 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"Version"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ +public class RFC2965VersionAttributeHandler implements CookieAttributeHandler { + + public RFC2965VersionAttributeHandler() { + super(); + } + + /** + * Parse cookie version attribute. + */ + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException( + "Missing value for version attribute"); + } + int version = -1; + try { + version = Integer.parseInt(value); + } catch (NumberFormatException e) { + version = -1; + } + if (version < 0) { + throw new MalformedCookieException("Invalid cookie version."); + } + cookie.setVersion(version); + } + + /** + * validate cookie version attribute. Version attribute is REQUIRED. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (cookie instanceof SetCookie2) { + if (cookie instanceof ClientCookie + && !((ClientCookie) cookie).containsAttribute(ClientCookie.VERSION_ATTR)) { + throw new MalformedCookieException( + "Violates RFC 2965. Version attribute is required."); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + +}
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/package.html b/src/org/apache/http/impl/cookie/package.html new file mode 100644 index 0000000..e301283 --- /dev/null +++ b/src/org/apache/http/impl/cookie/package.html @@ -0,0 +1,4 @@ +<body> + +</body> + diff --git a/src/org/apache/http/impl/entity/EntityDeserializer.java b/src/org/apache/http/impl/entity/EntityDeserializer.java new file mode 100644 index 0000000..12c4756 --- /dev/null +++ b/src/org/apache/http/impl/entity/EntityDeserializer.java @@ -0,0 +1,115 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/EntityDeserializer.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import java.io.IOException; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.impl.io.ChunkedInputStream; +import org.apache.http.impl.io.ContentLengthInputStream; +import org.apache.http.impl.io.IdentityInputStream; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.protocol.HTTP; + +/** + * Default implementation of an entity deserializer. + * <p> + * This entity deserializer currently supports only "chunked" and "identitiy" transfer-coding</a> + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class EntityDeserializer { + + private final ContentLengthStrategy lenStrategy; + + public EntityDeserializer(final ContentLengthStrategy lenStrategy) { + super(); + if (lenStrategy == null) { + throw new IllegalArgumentException("Content length strategy may not be null"); + } + this.lenStrategy = lenStrategy; + } + + protected BasicHttpEntity doDeserialize( + final SessionInputBuffer inbuffer, + final HttpMessage message) throws HttpException, IOException { + BasicHttpEntity entity = new BasicHttpEntity(); + + long len = this.lenStrategy.determineLength(message); + if (len == ContentLengthStrategy.CHUNKED) { + entity.setChunked(true); + entity.setContentLength(-1); + entity.setContent(new ChunkedInputStream(inbuffer)); + } else if (len == ContentLengthStrategy.IDENTITY) { + entity.setChunked(false); + entity.setContentLength(-1); + entity.setContent(new IdentityInputStream(inbuffer)); + } else { + entity.setChunked(false); + entity.setContentLength(len); + entity.setContent(new ContentLengthInputStream(inbuffer, len)); + } + + Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE); + if (contentTypeHeader != null) { + entity.setContentType(contentTypeHeader); + } + Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING); + if (contentEncodingHeader != null) { + entity.setContentEncoding(contentEncodingHeader); + } + return entity; + } + + public HttpEntity deserialize( + final SessionInputBuffer inbuffer, + final HttpMessage message) throws HttpException, IOException { + if (inbuffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + return doDeserialize(inbuffer, message); + } + +} diff --git a/src/org/apache/http/impl/entity/EntitySerializer.java b/src/org/apache/http/impl/entity/EntitySerializer.java new file mode 100644 index 0000000..3221980 --- /dev/null +++ b/src/org/apache/http/impl/entity/EntitySerializer.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/EntitySerializer.java $ + * $Revision: 560343 $ + * $Date: 2007-07-27 11:18:19 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.impl.io.ChunkedOutputStream; +import org.apache.http.impl.io.ContentLengthOutputStream; +import org.apache.http.impl.io.IdentityOutputStream; +import org.apache.http.io.SessionOutputBuffer; + +/** + * Default implementation of an entity serializer. + * <p> + * This entity serializer currently supports only "chunked" and "identitiy" transfer-coding</a> + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560343 $ + * + * @since 4.0 + */ +public class EntitySerializer { + + private final ContentLengthStrategy lenStrategy; + + public EntitySerializer(final ContentLengthStrategy lenStrategy) { + super(); + if (lenStrategy == null) { + throw new IllegalArgumentException("Content length strategy may not be null"); + } + this.lenStrategy = lenStrategy; + } + + protected OutputStream doSerialize( + final SessionOutputBuffer outbuffer, + final HttpMessage message) throws HttpException, IOException { + long len = this.lenStrategy.determineLength(message); + if (len == ContentLengthStrategy.CHUNKED) { + return new ChunkedOutputStream(outbuffer); + } else if (len == ContentLengthStrategy.IDENTITY) { + return new IdentityOutputStream(outbuffer); + } else { + return new ContentLengthOutputStream(outbuffer, len); + } + } + + public void serialize( + final SessionOutputBuffer outbuffer, + final HttpMessage message, + final HttpEntity entity) throws HttpException, IOException { + if (outbuffer == null) { + throw new IllegalArgumentException("Session output buffer may not be null"); + } + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + if (entity == null) { + throw new IllegalArgumentException("HTTP entity may not be null"); + } + OutputStream outstream = doSerialize(outbuffer, message); + entity.writeTo(outstream); + outstream.close(); + } + +} diff --git a/src/org/apache/http/impl/entity/LaxContentLengthStrategy.java b/src/org/apache/http/impl/entity/LaxContentLengthStrategy.java new file mode 100644 index 0000000..9a0d238 --- /dev/null +++ b/src/org/apache/http/impl/entity/LaxContentLengthStrategy.java @@ -0,0 +1,260 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/LaxContentLengthStrategy.java $ + * $Revision: 576073 $ + * $Date: 2007-09-16 03:53:13 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.ParseException; +import org.apache.http.ProtocolException; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.params.HttpParams; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.protocol.HTTP; + +/** + * The lax implementation of the content length strategy. + * <p> + * This strategy conforms to the entity transfer rules outlined in + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec4.4">Section 4.4</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41">Section 14.41</a> + * and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec14.13">Section 14.13</a> + * of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, but is lenient + * about unsupported transfer codecs and malformed content-length headers. + * </p> + * <h>4.4 Message Length</h> + * <p> + * The transfer-length of a message is the length of the message-body as it appears in the + * message; that is, after any transfer-codings have been applied. When a message-body is + * included with a message, the transfer-length of that body is determined by one of the + * following (in order of precedence): + * </p> + * <p> + * 1.Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, + * and 304 responses and any response to a HEAD request) is always terminated by the first + * empty line after the header fields, regardless of the entity-header fields present in the + * message. + * </p> + * <p> + * 2.If a Transfer-Encoding header field (section 14.41) is present and has any value other + * than "identity", then the transfer-length is defined by use of the "chunked" transfer- + * coding (section 3.6), unless the message is terminated by closing the connection. + * </p> + * <p> + * 3.If a Content-Length header field (section 14.13) is present, its decimal value in + * OCTETs represents both the entity-length and the transfer-length. The Content-Length + * header field MUST NOT be sent if these two lengths are different (i.e., if a + * Transfer-Encoding + * </p> + * <pre> + * header field is present). If a message is received with both a + * Transfer-Encoding header field and a Content-Length header field, + * the latter MUST be ignored. + * </pre> + * <p> + * 4.If the message uses the media type "multipart/byteranges", and the ransfer-length is not + * otherwise specified, then this self- elimiting media type defines the transfer-length. + * This media type UST NOT be used unless the sender knows that the recipient can arse it; the + * presence in a request of a Range header with ultiple byte- range specifiers from a 1.1 + * client implies that the lient can parse multipart/byteranges responses. + * </p> + * <pre> + * A range header might be forwarded by a 1.0 proxy that does not + * understand multipart/byteranges; in this case the server MUST + * delimit the message using methods defined in items 1,3 or 5 of + * this section. + * </pre> + * <p> + * 5.By the server closing the connection. (Closing the connection cannot be used to indicate + * the end of a request body, since that would leave no possibility for the server to send back + * a response.) + * </p> + * <p> + * For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body + * MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 + * compliant. If a request contains a message-body and a Content-Length is not given, the + * server SHOULD respond with 400 (bad request) if it cannot determine the length of the + * message, or with 411 (length required) if it wishes to insist on receiving a valid + * Content-Length. + * </p> + * <p>All HTTP/1.1 applications that receive entities MUST accept the "chunked" transfer-coding + * (section 3.6), thus allowing this mechanism to be used for messages when the message + * length cannot be determined in advance. + * </p> + * <h>3.6 Transfer Codings</h> + * <p> + * Transfer-coding values are used to indicate an encoding transformation that + * has been, can be, or may need to be applied to an entity-body in order to ensure + * "safe transport" through the network. This differs from a content coding in that + * the transfer-coding is a property of the message, not of the original entity. + * </p> + * <pre> + * transfer-coding = "chunked" | transfer-extension + * transfer-extension = token *( ";" parameter ) + * </pre> + * <p> + * Parameters are in the form of attribute/value pairs. + * </p> + * <pre> + * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + * </pre> + * <p> + * All transfer-coding values are case-insensitive. HTTP/1.1 uses transfer-coding values in + * the TE header field (section 14.39) and in the Transfer-Encoding header field (section 14.41). + * </p> + * <p> + * Whenever a transfer-coding is applied to a message-body, the set of transfer-codings MUST + * include "chunked", unless the message is terminated by closing the connection. When the + * "chunked" transfer-coding is used, it MUST be the last transfer-coding applied to the + * message-body. The "chunked" transfer-coding MUST NOT be applied more than once to a + * message-body. These rules allow the recipient to determine the transfer-length of the + * message (section 4.4). + * </p> + * <h>14.41 Transfer-Encoding</h> + * <p> + * The Transfer-Encoding general-header field indicates what (if any) type of transformation has + * been applied to the message body in order to safely transfer it between the sender and the + * recipient. This differs from the content-coding in that the transfer-coding is a property of + * the message, not of the entity. + * </p> + * <pre> + * Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding + * </pre> + * <p> + * If multiple encodings have been applied to an entity, the transfer- codings MUST be listed in + * the order in which they were applied. Additional information about the encoding parameters + * MAY be provided by other entity-header fields not defined by this specification. + * </p> + * <h>14.13 Content-Length</h> + * <p> + * The Content-Length entity-header field indicates the size of the entity-body, in decimal + * number of OCTETs, sent to the recipient or, in the case of the HEAD method, the size of + * the entity-body that would have been sent had the request been a GET. + * </p> + * <pre> + * Content-Length = "Content-Length" ":" 1*DIGIT + * </pre> + * <p> + * Applications SHOULD use this field to indicate the transfer-length of the message-body, + * unless this is prohibited by the rules in section 4.4. + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 576073 $ + * + * @since 4.0 + */ +public class LaxContentLengthStrategy implements ContentLengthStrategy { + + public LaxContentLengthStrategy() { + super(); + } + + public long determineLength(final HttpMessage message) throws HttpException { + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + + HttpParams params = message.getParams(); + boolean strict = params.isParameterTrue(CoreProtocolPNames.STRICT_TRANSFER_ENCODING); + + Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING); + Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN); + // We use Transfer-Encoding if present and ignore Content-Length. + // RFC2616, 4.4 item number 3 + if (transferEncodingHeader != null) { + HeaderElement[] encodings = null; + try { + encodings = transferEncodingHeader.getElements(); + } catch (ParseException px) { + throw new ProtocolException + ("Invalid Transfer-Encoding header value: " + + transferEncodingHeader, px); + } + if (strict) { + // Currently only chunk and identity are supported + for (int i = 0; i < encodings.length; i++) { + String encoding = encodings[i].getName(); + if (encoding != null && encoding.length() > 0 + && !encoding.equalsIgnoreCase(HTTP.CHUNK_CODING) + && !encoding.equalsIgnoreCase(HTTP.IDENTITY_CODING)) { + throw new ProtocolException("Unsupported transfer encoding: " + encoding); + } + } + } + // The chunked encoding must be the last one applied RFC2616, 14.41 + int len = encodings.length; + if (HTTP.IDENTITY_CODING.equalsIgnoreCase(transferEncodingHeader.getValue())) { + return IDENTITY; + } else if ((len > 0) && (HTTP.CHUNK_CODING.equalsIgnoreCase( + encodings[len - 1].getName()))) { + return CHUNKED; + } else { + if (strict) { + throw new ProtocolException("Chunk-encoding must be the last one applied"); + } + return IDENTITY; + } + } else if (contentLengthHeader != null) { + long contentlen = -1; + Header[] headers = message.getHeaders(HTTP.CONTENT_LEN); + if (strict && headers.length > 1) { + throw new ProtocolException("Multiple content length headers"); + } + for (int i = headers.length - 1; i >= 0; i--) { + Header header = headers[i]; + try { + contentlen = Long.parseLong(header.getValue()); + break; + } catch (NumberFormatException e) { + if (strict) { + throw new ProtocolException("Invalid content length: " + header.getValue()); + } + } + // See if we can have better luck with another header, if present + } + if (contentlen >= 0) { + return contentlen; + } else { + return IDENTITY; + } + } else { + return IDENTITY; + } + } + +} diff --git a/src/org/apache/http/impl/entity/StrictContentLengthStrategy.java b/src/org/apache/http/impl/entity/StrictContentLengthStrategy.java new file mode 100644 index 0000000..30be8e2 --- /dev/null +++ b/src/org/apache/http/impl/entity/StrictContentLengthStrategy.java @@ -0,0 +1,220 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/StrictContentLengthStrategy.java $ + * $Revision: 573949 $ + * $Date: 2007-09-08 22:46:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolException; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.protocol.HTTP; + +/** + * The strict implementation of the content length strategy. + * <p> + * This entity generator comforms to the entity transfer rules outlined in the + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec4.4">Section 4.4</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41">Section 14.41</a> + * and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec14.13">Section 14.13</a> + * of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a> + * </p> + * <h>4.4 Message Length</h> + * <p> + * The transfer-length of a message is the length of the message-body as it appears in the + * message; that is, after any transfer-codings have been applied. When a message-body is + * included with a message, the transfer-length of that body is determined by one of the + * following (in order of precedence): + * </p> + * <p> + * 1.Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, + * and 304 responses and any response to a HEAD request) is always terminated by the first + * empty line after the header fields, regardless of the entity-header fields present in the + * message. + * </p> + * <p> + * 2.If a Transfer-Encoding header field (section 14.41) is present and has any value other + * than "identity", then the transfer-length is defined by use of the "chunked" transfer- + * coding (section 3.6), unless the message is terminated by closing the connection. + * </p> + * <p> + * 3.If a Content-Length header field (section 14.13) is present, its decimal value in + * OCTETs represents both the entity-length and the transfer-length. The Content-Length + * header field MUST NOT be sent if these two lengths are different (i.e., if a + * Transfer-Encoding + * </p> + * <pre> + * header field is present). If a message is received with both a + * Transfer-Encoding header field and a Content-Length header field, + * the latter MUST be ignored. + * </pre> + * <p> + * 4.If the message uses the media type "multipart/byteranges", and the ransfer-length is not + * otherwise specified, then this self- elimiting media type defines the transfer-length. + * This media type UST NOT be used unless the sender knows that the recipient can arse it; the + * presence in a request of a Range header with ultiple byte- range specifiers from a 1.1 + * client implies that the lient can parse multipart/byteranges responses. + * </p> + * <pre> + * A range header might be forwarded by a 1.0 proxy that does not + * understand multipart/byteranges; in this case the server MUST + * delimit the message using methods defined in items 1,3 or 5 of + * this section. + * </pre> + * <p> + * 5.By the server closing the connection. (Closing the connection cannot be used to indicate + * the end of a request body, since that would leave no possibility for the server to send back + * a response.) + * </p> + * <p> + * For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body + * MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 + * compliant. If a request contains a message-body and a Content-Length is not given, the + * server SHOULD respond with 400 (bad request) if it cannot determine the length of the + * message, or with 411 (length required) if it wishes to insist on receiving a valid + * Content-Length. + * </p> + * <p>All HTTP/1.1 applications that receive entities MUST accept the "chunked" transfer-coding + * (section 3.6), thus allowing this mechanism to be used for messages when the message + * length cannot be determined in advance. + * </p> + * <h>3.6 Transfer Codings</h> + * <p> + * Transfer-coding values are used to indicate an encoding transformation that + * has been, can be, or may need to be applied to an entity-body in order to ensure + * "safe transport" through the network. This differs from a content coding in that + * the transfer-coding is a property of the message, not of the original entity. + * </p> + * <pre> + * transfer-coding = "chunked" | transfer-extension + * transfer-extension = token *( ";" parameter ) + * </pre> + * <p> + * Parameters are in the form of attribute/value pairs. + * </p> + * <pre> + * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + * </pre> + * <p> + * All transfer-coding values are case-insensitive. HTTP/1.1 uses transfer-coding values in + * the TE header field (section 14.39) and in the Transfer-Encoding header field (section 14.41). + * </p> + * <p> + * Whenever a transfer-coding is applied to a message-body, the set of transfer-codings MUST + * include "chunked", unless the message is terminated by closing the connection. When the + * "chunked" transfer-coding is used, it MUST be the last transfer-coding applied to the + * message-body. The "chunked" transfer-coding MUST NOT be applied more than once to a + * message-body. These rules allow the recipient to determine the transfer-length of the + * message (section 4.4). + * </p> + * <h>14.41 Transfer-Encoding</h> + * <p> + * The Transfer-Encoding general-header field indicates what (if any) type of transformation has + * been applied to the message body in order to safely transfer it between the sender and the + * recipient. This differs from the content-coding in that the transfer-coding is a property of + * the message, not of the entity. + * </p> + * <pre> + * Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding + * </pre> + * <p> + * If multiple encodings have been applied to an entity, the transfer- codings MUST be listed in + * the order in which they were applied. Additional information about the encoding parameters + * MAY be provided by other entity-header fields not defined by this specification. + * </p> + * <h>14.13 Content-Length</h> + * <p> + * The Content-Length entity-header field indicates the size of the entity-body, in decimal + * number of OCTETs, sent to the recipient or, in the case of the HEAD method, the size of + * the entity-body that would have been sent had the request been a GET. + * </p> + * <pre> + * Content-Length = "Content-Length" ":" 1*DIGIT + * </pre> + * <p> + * Applications SHOULD use this field to indicate the transfer-length of the message-body, + * unless this is prohibited by the rules in section 4.4. + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573949 $ + * + * @since 4.0 + */ +public class StrictContentLengthStrategy implements ContentLengthStrategy { + + public StrictContentLengthStrategy() { + super(); + } + + public long determineLength(final HttpMessage message) throws HttpException { + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + // Although Transfer-Encoding is specified as a list, in practice + // it is either missing or has the single value "chunked". So we + // treat it as a single-valued header here. + Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING); + Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN); + if (transferEncodingHeader != null) { + String s = transferEncodingHeader.getValue(); + if (HTTP.CHUNK_CODING.equalsIgnoreCase(s)) { + if (message.getProtocolVersion().lessEquals(HttpVersion.HTTP_1_0)) { + throw new ProtocolException( + "Chunked transfer encoding not allowed for " + + message.getProtocolVersion()); + } + return CHUNKED; + } else if (HTTP.IDENTITY_CODING.equalsIgnoreCase(s)) { + return IDENTITY; + } else { + throw new ProtocolException( + "Unsupported transfer encoding: " + s); + } + } else if (contentLengthHeader != null) { + String s = contentLengthHeader.getValue(); + try { + long len = Long.parseLong(s); + return len; + } catch (NumberFormatException e) { + throw new ProtocolException("Invalid content length: " + s); + } + } else { + return IDENTITY; + } + } + +} diff --git a/src/org/apache/http/impl/entity/package.html b/src/org/apache/http/impl/entity/package.html new file mode 100644 index 0000000..9ac4481 --- /dev/null +++ b/src/org/apache/http/impl/entity/package.html @@ -0,0 +1,41 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/package.html $ + * $Revision: 496072 $ + * $Date: 2007-01-14 04:22:55 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Default implementations for interfaces in +{@link org.apache.http.entity org.apache.http.entity}. + +</body> +</html> diff --git a/src/org/apache/http/impl/io/AbstractMessageParser.java b/src/org/apache/http/impl/io/AbstractMessageParser.java new file mode 100644 index 0000000..679bcd1 --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractMessageParser.java @@ -0,0 +1,187 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractMessageParser.java $ + * $Revision: 576077 $ + * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.util.ArrayList; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.ParseException; +import org.apache.http.ProtocolException; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.BasicLineParser; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +/** + * Message parser base class. + * + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public abstract class AbstractMessageParser implements HttpMessageParser { + + private final SessionInputBuffer sessionBuffer; + private final int maxHeaderCount; + private final int maxLineLen; + protected final LineParser lineParser; + + + public AbstractMessageParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpParams params) { + super(); + if (buffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.sessionBuffer = buffer; + this.maxHeaderCount = params.getIntParameter( + CoreConnectionPNames.MAX_HEADER_COUNT, -1); + this.maxLineLen = params.getIntParameter( + CoreConnectionPNames.MAX_LINE_LENGTH, -1); + this.lineParser = (parser != null) ? parser : BasicLineParser.DEFAULT; + } + + /** + * Parses HTTP headers from the data receiver stream according to the generic + * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. + * + * @param inbuffer Session input buffer + * @param maxHeaderCount maximum number of headers allowed. If the number + * of headers received from the data stream exceeds maxCount value, an + * IOException will be thrown. Setting this parameter to a negative value + * or zero will disable the check. + * @param maxLineLen maximum number of characters for a header line, + * including the continuation lines + * @return array of HTTP headers + * + * @throws HttpException + * @throws IOException + */ + public static Header[] parseHeaders( + final SessionInputBuffer inbuffer, + int maxHeaderCount, + int maxLineLen, + LineParser parser) + throws HttpException, IOException { + + if (inbuffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + if (parser == null) + parser = BasicLineParser.DEFAULT; + + ArrayList headerLines = new ArrayList(); + + CharArrayBuffer current = null; + CharArrayBuffer previous = null; + for (;;) { + if (current == null) { + current = new CharArrayBuffer(64); + } else { + current.clear(); + } + int l = inbuffer.readLine(current); + if (l == -1 || current.length() < 1) { + break; + } + // Parse the header name and value + // Check for folded headers first + // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 + // discussion on folded headers + if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) { + // we have continuation folded header + // so append value + int i = 0; + while (i < current.length()) { + char ch = current.charAt(i); + if (ch != ' ' && ch != '\t') { + break; + } + i++; + } + if (maxLineLen > 0 + && previous.length() + 1 + current.length() - i > maxLineLen) { + throw new IOException("Maximum line length limit exceeded"); + } + previous.append(' '); + previous.append(current, i, current.length() - i); + } else { + headerLines.add(current); + previous = current; + current = null; + } + if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) { + throw new IOException("Maximum header count exceeded"); + } + } + Header[] headers = new Header[headerLines.size()]; + for (int i = 0; i < headerLines.size(); i++) { + CharArrayBuffer buffer = (CharArrayBuffer) headerLines.get(i); + try { + headers[i] = parser.parseHeader(buffer); + } catch (ParseException ex) { + throw new ProtocolException(ex.getMessage()); + } + } + return headers; + } + + protected abstract HttpMessage parseHead(SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException; + + public HttpMessage parse() throws IOException, HttpException { + HttpMessage message = null; + try { + message = parseHead(this.sessionBuffer); + } catch (ParseException px) { + throw new ProtocolException(px.getMessage(), px); + } + Header[] headers = AbstractMessageParser.parseHeaders( + this.sessionBuffer, + this.maxHeaderCount, + this.maxLineLen, + this.lineParser); + message.setHeaders(headers); + return message; + } + +} diff --git a/src/org/apache/http/impl/io/AbstractMessageWriter.java b/src/org/apache/http/impl/io/AbstractMessageWriter.java new file mode 100644 index 0000000..f9644ce --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractMessageWriter.java @@ -0,0 +1,85 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractMessageWriter.java $ + * $Revision: 569673 $ + * $Date: 2007-08-25 06:58:51 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.util.Iterator; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.io.HttpMessageWriter; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.message.LineFormatter; +import org.apache.http.message.BasicLineFormatter; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public abstract class AbstractMessageWriter implements HttpMessageWriter { + + protected final SessionOutputBuffer sessionBuffer; + protected final CharArrayBuffer lineBuf; + protected final LineFormatter lineFormatter; + + public AbstractMessageWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(); + if (buffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + this.sessionBuffer = buffer; + this.lineBuf = new CharArrayBuffer(128); + this.lineFormatter = (formatter != null) ? + formatter : BasicLineFormatter.DEFAULT; + } + + protected abstract void writeHeadLine(HttpMessage message) + throws IOException + ; + + public void write( + final HttpMessage message) throws IOException, HttpException { + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + writeHeadLine(message); + for (Iterator it = message.headerIterator(); it.hasNext(); ) { + Header header = (Header) it.next(); + this.sessionBuffer.writeLine + (lineFormatter.formatHeader(this.lineBuf, header)); + } + this.lineBuf.clear(); + this.sessionBuffer.writeLine(this.lineBuf); + } + +} diff --git a/src/org/apache/http/impl/io/AbstractSessionInputBuffer.java b/src/org/apache/http/impl/io/AbstractSessionInputBuffer.java new file mode 100644 index 0000000..eb007a9 --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractSessionInputBuffer.java @@ -0,0 +1,271 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java $ + * $Revision: 576077 $ + * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.ByteArrayBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract base class for session input buffers that stream data + * from a {@link InputStream}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + */ +public abstract class AbstractSessionInputBuffer implements SessionInputBuffer { + + private InputStream instream; + private byte[] buffer; + private int bufferpos; + private int bufferlen; + + private ByteArrayBuffer linebuffer = null; + + private String charset = HTTP.US_ASCII; + private boolean ascii = true; + private int maxLineLen = -1; + + private HttpTransportMetricsImpl metrics; + + protected void init(final InputStream instream, int buffersize, final HttpParams params) { + if (instream == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (buffersize <= 0) { + throw new IllegalArgumentException("Buffer size may not be negative or zero"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.instream = instream; + this.buffer = new byte[buffersize]; + this.bufferpos = 0; + this.bufferlen = 0; + this.linebuffer = new ByteArrayBuffer(buffersize); + this.charset = HttpProtocolParams.getHttpElementCharset(params); + this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII) + || this.charset.equalsIgnoreCase(HTTP.ASCII); + this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1); + this.metrics = new HttpTransportMetricsImpl(); + } + + protected int fillBuffer() throws IOException { + // compact the buffer if necessary + if (this.bufferpos > 0) { + int len = this.bufferlen - this.bufferpos; + if (len > 0) { + System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len); + } + this.bufferpos = 0; + this.bufferlen = len; + } + int l; + int off = this.bufferlen; + int len = this.buffer.length - off; + l = this.instream.read(this.buffer, off, len); + if (l == -1) { + return -1; + } else { + this.bufferlen = off + l; + this.metrics.incrementBytesTransferred(l); + return l; + } + } + + protected boolean hasBufferedData() { + return this.bufferpos < this.bufferlen; + } + + public int read() throws IOException { + int noRead = 0; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + return this.buffer[this.bufferpos++] & 0xff; + } + + public int read(final byte[] b, int off, int len) throws IOException { + if (b == null) { + return 0; + } + int noRead = 0; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + int chunk = this.bufferlen - this.bufferpos; + if (chunk > len) { + chunk = len; + } + System.arraycopy(this.buffer, this.bufferpos, b, off, chunk); + this.bufferpos += chunk; + return chunk; + } + + public int read(final byte[] b) throws IOException { + if (b == null) { + return 0; + } + return read(b, 0, b.length); + } + + private int locateLF() { + for (int i = this.bufferpos; i < this.bufferlen; i++) { + if (this.buffer[i] == HTTP.LF) { + return i; + } + } + return -1; + } + + public int readLine(final CharArrayBuffer charbuffer) throws IOException { + if (charbuffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + this.linebuffer.clear(); + int noRead = 0; + boolean retry = true; + while (retry) { + // attempt to find end of line (LF) + int i = locateLF(); + if (i != -1) { + // end of line found. + if (this.linebuffer.isEmpty()) { + // the entire line is preset in the read buffer + return lineFromReadBuffer(charbuffer, i); + } + retry = false; + int len = i + 1 - this.bufferpos; + this.linebuffer.append(this.buffer, this.bufferpos, len); + this.bufferpos = i + 1; + } else { + // end of line not found + if (hasBufferedData()) { + int len = this.bufferlen - this.bufferpos; + this.linebuffer.append(this.buffer, this.bufferpos, len); + this.bufferpos = this.bufferlen; + } + noRead = fillBuffer(); + if (noRead == -1) { + retry = false; + } + } + if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) { + throw new IOException("Maximum line length limit exceeded"); + } + } + if (noRead == -1 && this.linebuffer.isEmpty()) { + // indicate the end of stream + return -1; + } + return lineFromLineBuffer(charbuffer); + } + + private int lineFromLineBuffer(final CharArrayBuffer charbuffer) + throws IOException { + // discard LF if found + int l = this.linebuffer.length(); + if (l > 0) { + if (this.linebuffer.byteAt(l - 1) == HTTP.LF) { + l--; + this.linebuffer.setLength(l); + } + // discard CR if found + if (l > 0) { + if (this.linebuffer.byteAt(l - 1) == HTTP.CR) { + l--; + this.linebuffer.setLength(l); + } + } + } + l = this.linebuffer.length(); + if (this.ascii) { + charbuffer.append(this.linebuffer, 0, l); + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + String s = new String(this.linebuffer.buffer(), 0, l, this.charset); + charbuffer.append(s); + } + return l; + } + + private int lineFromReadBuffer(final CharArrayBuffer charbuffer, int pos) + throws IOException { + int off = this.bufferpos; + int len; + this.bufferpos = pos + 1; + if (pos > 0 && this.buffer[pos - 1] == HTTP.CR) { + // skip CR if found + pos--; + } + len = pos - off; + if (this.ascii) { + charbuffer.append(this.buffer, off, len); + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + String s = new String(this.buffer, off, len, this.charset); + charbuffer.append(s); + } + return len; + } + + public String readLine() throws IOException { + CharArrayBuffer charbuffer = new CharArrayBuffer(64); + int l = readLine(charbuffer); + if (l != -1) { + return charbuffer.toString(); + } else { + return null; + } + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/io/AbstractSessionOutputBuffer.java b/src/org/apache/http/impl/io/AbstractSessionOutputBuffer.java new file mode 100644 index 0000000..bf4e56e --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractSessionOutputBuffer.java @@ -0,0 +1,179 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractSessionOutputBuffer.java $ + * $Revision: 652091 $ + * $Date: 2008-04-29 13:41:07 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.ByteArrayBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract base class for session output buffers that stream data + * to an {@link OutputStream}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + */ +public abstract class AbstractSessionOutputBuffer implements SessionOutputBuffer { + + private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF}; + + private static final int MAX_CHUNK = 256; + + private OutputStream outstream; + private ByteArrayBuffer buffer; + + private String charset = HTTP.US_ASCII; + private boolean ascii = true; + + private HttpTransportMetricsImpl metrics; + + protected void init(final OutputStream outstream, int buffersize, final HttpParams params) { + if (outstream == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (buffersize <= 0) { + throw new IllegalArgumentException("Buffer size may not be negative or zero"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.outstream = outstream; + this.buffer = new ByteArrayBuffer(buffersize); + this.charset = HttpProtocolParams.getHttpElementCharset(params); + this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII) + || this.charset.equalsIgnoreCase(HTTP.ASCII); + this.metrics = new HttpTransportMetricsImpl(); + } + + protected void flushBuffer() throws IOException { + int len = this.buffer.length(); + if (len > 0) { + this.outstream.write(this.buffer.buffer(), 0, len); + this.buffer.clear(); + this.metrics.incrementBytesTransferred(len); + } + } + + public void flush() throws IOException { + flushBuffer(); + this.outstream.flush(); + } + + public void write(final byte[] b, int off, int len) throws IOException { + if (b == null) { + return; + } + // Do not want to buffer largish chunks + // if the byte array is larger then MAX_CHUNK + // write it directly to the output stream + if (len > MAX_CHUNK || len > this.buffer.capacity()) { + // flush the buffer + flushBuffer(); + // write directly to the out stream + this.outstream.write(b, off, len); + this.metrics.incrementBytesTransferred(len); + } else { + // Do not let the buffer grow unnecessarily + int freecapacity = this.buffer.capacity() - this.buffer.length(); + if (len > freecapacity) { + // flush the buffer + flushBuffer(); + } + // buffer + this.buffer.append(b, off, len); + } + } + + public void write(final byte[] b) throws IOException { + if (b == null) { + return; + } + write(b, 0, b.length); + } + + public void write(int b) throws IOException { + if (this.buffer.isFull()) { + flushBuffer(); + } + this.buffer.append(b); + } + + public void writeLine(final String s) throws IOException { + if (s == null) { + return; + } + if (s.length() > 0) { + write(s.getBytes(this.charset)); + } + write(CRLF); + } + + public void writeLine(final CharArrayBuffer s) throws IOException { + if (s == null) { + return; + } + if (this.ascii) { + int off = 0; + int remaining = s.length(); + while (remaining > 0) { + int chunk = this.buffer.capacity() - this.buffer.length(); + chunk = Math.min(chunk, remaining); + if (chunk > 0) { + this.buffer.append(s, off, chunk); + } + if (this.buffer.isFull()) { + flushBuffer(); + } + off += chunk; + remaining -= chunk; + } + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + byte[] tmp = s.toString().getBytes(this.charset); + write(tmp); + } + write(CRLF); + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/io/ChunkedInputStream.java b/src/org/apache/http/impl/io/ChunkedInputStream.java new file mode 100644 index 0000000..60cae90 --- /dev/null +++ b/src/org/apache/http/impl/io/ChunkedInputStream.java @@ -0,0 +1,294 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java $ + * $Revision: 569843 $ + * $Date: 2007-08-26 10:05:40 -0700 (Sun, 26 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.MalformedChunkCodingException; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.ExceptionUtils; + +/** + * Implements chunked transfer coding. + * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>. + * It transparently coalesces chunks of a HTTP stream that uses chunked + * transfer coding. After the stream is read to the end, it provides access + * to the trailers, if any. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its chunking on + * close, which allows for the seamless execution of subsequent HTTP 1.1 + * requests, while not requiring the client to remember to read the entire + * contents of the response. + * </p> + * + * @author Ortwin Glueck + * @author Sean C. Sullivan + * @author Martin Elwin + * @author Eric Johnson + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + * + */ +public class ChunkedInputStream extends InputStream { + + /** The session input buffer */ + private SessionInputBuffer in; + + private final CharArrayBuffer buffer; + + /** The chunk size */ + private int chunkSize; + + /** The current position within the current chunk */ + private int pos; + + /** True if we'are at the beginning of stream */ + private boolean bof = true; + + /** True if we've reached the end of stream */ + private boolean eof = false; + + /** True if this stream is closed */ + private boolean closed = false; + + private Header[] footers = new Header[] {}; + + public ChunkedInputStream(final SessionInputBuffer in) { + super(); + if (in == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + this.in = in; + this.pos = 0; + this.buffer = new CharArrayBuffer(16); + } + + /** + * <p> Returns all the data in a chunked stream in coalesced form. A chunk + * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 + * is detected.</p> + * + * <p> Trailer headers are read automcatically at the end of the stream and + * can be obtained with the getResponseFooters() method.</p> + * + * @return -1 of the end of the stream has been reached or the next data + * byte + * @throws IOException If an IO problem occurs + */ + public int read() throws IOException { + if (this.closed) { + throw new IOException("Attempted read from closed stream."); + } + if (this.eof) { + return -1; + } + if (this.pos >= this.chunkSize) { + nextChunk(); + if (this.eof) { + return -1; + } + } + pos++; + return in.read(); + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @param off The offset into the byte array at which bytes will start to be + * placed. + * @param len the maximum number of bytes that can be returned. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[], int, int) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b, int off, int len) throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (eof) { + return -1; + } + if (pos >= chunkSize) { + nextChunk(); + if (eof) { + return -1; + } + } + len = Math.min(len, chunkSize - pos); + int count = in.read(b, off, len); + pos += count; + return count; + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[]) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read the next chunk. + * @throws IOException If an IO error occurs. + */ + private void nextChunk() throws IOException { + chunkSize = getChunkSize(); + if (chunkSize < 0) { + throw new MalformedChunkCodingException("Negative chunk size"); + } + bof = false; + pos = 0; + if (chunkSize == 0) { + eof = true; + parseTrailerHeaders(); + } + } + + /** + * Expects the stream to start with a chunksize in hex with optional + * comments after a semicolon. The line must end with a CRLF: "a3; some + * comment\r\n" Positions the stream at the start of the next line. + * + * @param in The new input stream. + * @param required <tt>true<tt/> if a valid chunk must be present, + * <tt>false<tt/> otherwise. + * + * @return the chunk size as integer + * + * @throws IOException when the chunk size could not be parsed + */ + private int getChunkSize() throws IOException { + // skip CRLF + if (!bof) { + int cr = in.read(); + int lf = in.read(); + if ((cr != HTTP.CR) || (lf != HTTP.LF)) { + throw new MalformedChunkCodingException( + "CRLF expected at end of chunk"); + } + } + //parse data + this.buffer.clear(); + int i = this.in.readLine(this.buffer); + if (i == -1) { + throw new MalformedChunkCodingException( + "Chunked stream ended unexpectedly"); + } + int separator = this.buffer.indexOf(';'); + if (separator < 0) { + separator = this.buffer.length(); + } + try { + return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16); + } catch (NumberFormatException e) { + throw new MalformedChunkCodingException("Bad chunk header"); + } + } + + /** + * Reads and stores the Trailer headers. + * @throws IOException If an IO problem occurs + */ + private void parseTrailerHeaders() throws IOException { + try { + this.footers = AbstractMessageParser.parseHeaders + (in, -1, -1, null); + } catch (HttpException e) { + IOException ioe = new MalformedChunkCodingException("Invalid footer: " + + e.getMessage()); + ExceptionUtils.initCause(ioe, e); + throw ioe; + } + } + + /** + * Upon close, this reads the remainder of the chunked message, + * leaving the underlying socket at a position to start reading the + * next response without scanning. + * @throws IOException If an IO problem occurs. + */ + public void close() throws IOException { + if (!closed) { + try { + if (!eof) { + exhaustInputStream(this); + } + } finally { + eof = true; + closed = true; + } + } + } + + public Header[] getFooters() { + return (Header[])this.footers.clone(); + } + + /** + * Exhaust an input stream, reading until EOF has been encountered. + * + * <p>Note that this function is intended as a non-public utility. + * This is a little weird, but it seemed silly to make a utility + * class for this one function, so instead it is just static and + * shared that way.</p> + * + * @param inStream The {@link InputStream} to exhaust. + * @throws IOException If an IO problem occurs + */ + static void exhaustInputStream(final InputStream inStream) throws IOException { + // read and discard the remainder of the message + byte buffer[] = new byte[1024]; + while (inStream.read(buffer) >= 0) { + ; + } + } + +} diff --git a/src/org/apache/http/impl/io/ChunkedOutputStream.java b/src/org/apache/http/impl/io/ChunkedOutputStream.java new file mode 100644 index 0000000..5ee7dd6 --- /dev/null +++ b/src/org/apache/http/impl/io/ChunkedOutputStream.java @@ -0,0 +1,191 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java $ + * $Revision: 645081 $ + * $Date: 2008-04-05 04:36:42 -0700 (Sat, 05 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; + +/** + * Implements chunked transfer coding. + * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>. + * Writes are buffered to an internal buffer (2048 default size). + * + * @author Mohammad Rezaei (Goldman, Sachs & Co.) + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class ChunkedOutputStream extends OutputStream { + + // ----------------------------------------------------- Instance Variables + private final SessionOutputBuffer out; + + private byte[] cache; + + private int cachePosition = 0; + + private boolean wroteLastChunk = false; + + /** True if the stream is closed. */ + private boolean closed = false; + + // ----------------------------------------------------------- Constructors + /** + * Wraps a session output buffer and chunks the output. + * @param out the session output buffer to wrap + * @param bufferSize minimum chunk size (excluding last chunk) + * @throws IOException + */ + public ChunkedOutputStream(final SessionOutputBuffer out, int bufferSize) + throws IOException { + super(); + this.cache = new byte[bufferSize]; + this.out = out; + } + + /** + * Wraps a session output buffer and chunks the output. The default buffer + * size of 2048 was chosen because the chunk overhead is less than 0.5% + * + * @param out the output buffer to wrap + * @throws IOException + */ + public ChunkedOutputStream(final SessionOutputBuffer out) + throws IOException { + this(out, 2048); + } + + // ----------------------------------------------------------- Internal methods + /** + * Writes the cache out onto the underlying stream + * @throws IOException + */ + protected void flushCache() throws IOException { + if (this.cachePosition > 0) { + this.out.writeLine(Integer.toHexString(this.cachePosition)); + this.out.write(this.cache, 0, this.cachePosition); + this.out.writeLine(""); + this.cachePosition = 0; + } + } + + /** + * Writes the cache and bufferToAppend to the underlying stream + * as one large chunk + * @param bufferToAppend + * @param off + * @param len + * @throws IOException + */ + protected void flushCacheWithAppend(byte bufferToAppend[], int off, int len) throws IOException { + this.out.writeLine(Integer.toHexString(this.cachePosition + len)); + this.out.write(this.cache, 0, this.cachePosition); + this.out.write(bufferToAppend, off, len); + this.out.writeLine(""); + this.cachePosition = 0; + } + + protected void writeClosingChunk() throws IOException { + // Write the final chunk. + this.out.writeLine("0"); + this.out.writeLine(""); + } + + // ----------------------------------------------------------- Public Methods + /** + * Must be called to ensure the internal cache is flushed and the closing chunk is written. + * @throws IOException + */ + public void finish() throws IOException { + if (!this.wroteLastChunk) { + flushCache(); + writeClosingChunk(); + this.wroteLastChunk = true; + } + } + + // -------------------------------------------- OutputStream Methods + public void write(int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.cache[this.cachePosition] = (byte) b; + this.cachePosition++; + if (this.cachePosition == this.cache.length) flushCache(); + } + + /** + * Writes the array. If the array does not fit within the buffer, it is + * not split, but rather written out as one large chunk. + * @param b + * @throws IOException + */ + public void write(byte b[]) throws IOException { + write(b, 0, b.length); + } + + public void write(byte src[], int off, int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (len >= this.cache.length - this.cachePosition) { + flushCacheWithAppend(src, off, len); + } else { + System.arraycopy(src, off, cache, this.cachePosition, len); + this.cachePosition += len; + } + } + + /** + * Flushes the content buffer and the underlying stream. + * @throws IOException + */ + public void flush() throws IOException { + flushCache(); + this.out.flush(); + } + + /** + * Finishes writing to the underlying stream, but does NOT close the underlying stream. + * @throws IOException + */ + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + finish(); + this.out.flush(); + } + } +} diff --git a/src/org/apache/http/impl/io/ContentLengthInputStream.java b/src/org/apache/http/impl/io/ContentLengthInputStream.java new file mode 100644 index 0000000..3b19c5b --- /dev/null +++ b/src/org/apache/http/impl/io/ContentLengthInputStream.java @@ -0,0 +1,220 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java $ + * $Revision: 652091 $ + * $Date: 2008-04-29 13:41:07 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.io.SessionInputBuffer; + +/** + * Stream that cuts off after a specified number of bytes. + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its chunking on + * close, which allows for the seamless execution of subsequent HTTP 1.1 + * requests, while not requiring the client to remember to read the entire + * contents of the response. + * + * <p>Implementation note: Choices abound. One approach would pass + * through the {@link InputStream#mark} and {@link InputStream#reset} calls to + * the underlying stream. That's tricky, though, because you then have to + * start duplicating the work of keeping track of how much a reset rewinds. + * Further, you have to watch out for the "readLimit", and since the semantics + * for the readLimit leave room for differing implementations, you might get + * into a lot of trouble.</p> + * + * <p>Alternatively, you could make this class extend + * {@link java.io.BufferedInputStream} + * and then use the protected members of that class to avoid duplicated effort. + * That solution has the side effect of adding yet another possible layer of + * buffering.</p> + * + * <p>Then, there is the simple choice, which this takes - simply don't + * support {@link InputStream#mark} and {@link InputStream#reset}. That choice + * has the added benefit of keeping this class very simple.</p> + * + * @author Ortwin Glueck + * @author Eric Johnson + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @since 4.0 + */ +public class ContentLengthInputStream extends InputStream { + + private static final int BUFFER_SIZE = 2048; + /** + * The maximum number of bytes that can be read from the stream. Subsequent + * read operations will return -1. + */ + private long contentLength; + + /** The current position */ + private long pos = 0; + + /** True if the stream is closed. */ + private boolean closed = false; + + /** + * Wrapped input stream that all calls are delegated to. + */ + private SessionInputBuffer in = null; + + /** + * Creates a new length limited stream + * + * @param in The session input buffer to wrap + * @param contentLength The maximum number of bytes that can be read from + * the stream. Subsequent read operations will return -1. + */ + public ContentLengthInputStream(final SessionInputBuffer in, long contentLength) { + super(); + if (in == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (contentLength < 0) { + throw new IllegalArgumentException("Content length may not be negative"); + } + this.in = in; + this.contentLength = contentLength; + } + + /** + * <p>Reads until the end of the known length of content.</p> + * + * <p>Does not close the underlying socket input, but instead leaves it + * primed to parse the next response.</p> + * @throws IOException If an IO problem occurs. + */ + public void close() throws IOException { + if (!closed) { + try { + byte buffer[] = new byte[BUFFER_SIZE]; + while (read(buffer) >= 0) { + } + } finally { + // close after above so that we don't throw an exception trying + // to read after closed! + closed = true; + } + } + } + + + /** + * Read the next byte from the stream + * @return The next byte or -1 if the end of stream has been reached. + * @throws IOException If an IO problem occurs + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (pos >= contentLength) { + return -1; + } + pos++; + return this.in.read(); + } + + /** + * Does standard {@link InputStream#read(byte[], int, int)} behavior, but + * also notifies the watcher when the contents have been consumed. + * + * @param b The byte array to fill. + * @param off Start filling at this position. + * @param len The number of bytes to attempt to read. + * @return The number of bytes read, or -1 if the end of content has been + * reached. + * + * @throws java.io.IOException Should an error occur on the wrapped stream. + */ + public int read (byte[] b, int off, int len) throws java.io.IOException { + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (pos >= contentLength) { + return -1; + } + + if (pos + len > contentLength) { + len = (int) (contentLength - pos); + } + int count = this.in.read(b, off, len); + pos += count; + return count; + } + + + /** + * Read more bytes from the stream. + * @param b The byte array to put the new data in. + * @return The number of bytes read into the buffer. + * @throws IOException If an IO problem occurs + * @see java.io.InputStream#read(byte[]) + */ + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Skips and discards a number of bytes from the input stream. + * @param n The number of bytes to skip. + * @return The actual number of bytes skipped. <= 0 if no bytes + * are skipped. + * @throws IOException If an error occurs while skipping bytes. + * @see InputStream#skip(long) + */ + public long skip(long n) throws IOException { + if (n <= 0) { + return 0; + } + byte[] buffer = new byte[BUFFER_SIZE]; + // make sure we don't skip more bytes than are + // still available + long remaining = Math.min(n, this.contentLength - this.pos); + // skip and keep track of the bytes actually skipped + long count = 0; + while (remaining > 0) { + int l = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining)); + if (l == -1) { + break; + } + count += l; + remaining -= l; + } + this.pos += count; + return count; + } +} diff --git a/src/org/apache/http/impl/io/ContentLengthOutputStream.java b/src/org/apache/http/impl/io/ContentLengthOutputStream.java new file mode 100644 index 0000000..afcb883 --- /dev/null +++ b/src/org/apache/http/impl/io/ContentLengthOutputStream.java @@ -0,0 +1,132 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ContentLengthOutputStream.java $ + * $Revision: 560343 $ + * $Date: 2007-07-27 11:18:19 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; + +/** + * A stream wrapper that closes itself after a defined number of bytes. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560343 $ + * + * @since 4.0 + */ +public class ContentLengthOutputStream extends OutputStream { + + /** + * Wrapped session outbut buffer. + */ + private final SessionOutputBuffer out; + + /** + * The maximum number of bytes that can be written the stream. Subsequent + * write operations will be ignored. + */ + private final long contentLength; + + /** Total bytes written */ + private long total = 0; + + /** True if the stream is closed. */ + private boolean closed = false; + + /** + * Creates a new length limited stream + * + * @param out The data transmitter to wrap + * @param contentLength The maximum number of bytes that can be written to + * the stream. Subsequent write operations will be ignored. + * + * @since 4.0 + */ + public ContentLengthOutputStream(final SessionOutputBuffer out, long contentLength) { + super(); + if (out == null) { + throw new IllegalArgumentException("Session output buffer may not be null"); + } + if (contentLength < 0) { + throw new IllegalArgumentException("Content length may not be negative"); + } + this.out = out; + this.contentLength = contentLength; + } + + /** + * <p>Does not close the underlying socket output.</p> + * + * @throws IOException If an I/O problem occurs. + */ + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + this.out.flush(); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (this.total < this.contentLength) { + long max = this.contentLength - this.total; + if (len > max) { + len = (int) max; + } + this.out.write(b, off, len); + this.total += len; + } + } + + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + public void write(int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (this.total < this.contentLength) { + this.out.write(b); + this.total++; + } + } + +} diff --git a/src/org/apache/http/impl/io/HttpRequestParser.java b/src/org/apache/http/impl/io/HttpRequestParser.java new file mode 100644 index 0000000..a7bae6d --- /dev/null +++ b/src/org/apache/http/impl/io/HttpRequestParser.java @@ -0,0 +1,80 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpRequestParser.java $ + * $Revision: 589374 $ + * $Date: 2007-10-28 09:25:07 -0700 (Sun, 28 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.ConnectionClosedException; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpRequestFactory; +import org.apache.http.RequestLine; +import org.apache.http.ParseException; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpRequestParser extends AbstractMessageParser { + + private final HttpRequestFactory requestFactory; + private final CharArrayBuffer lineBuf; + + public HttpRequestParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpRequestFactory requestFactory, + final HttpParams params) { + super(buffer, parser, params); + if (requestFactory == null) { + throw new IllegalArgumentException("Request factory may not be null"); + } + this.requestFactory = requestFactory; + this.lineBuf = new CharArrayBuffer(128); + } + + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new ConnectionClosedException("Client closed connection"); + } + ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + RequestLine requestline = this.lineParser.parseRequestLine(this.lineBuf, cursor); + return this.requestFactory.newHttpRequest(requestline); + } + +} diff --git a/src/org/apache/http/impl/io/HttpRequestWriter.java b/src/org/apache/http/impl/io/HttpRequestWriter.java new file mode 100644 index 0000000..b784e2d --- /dev/null +++ b/src/org/apache/http/impl/io/HttpRequestWriter.java @@ -0,0 +1,59 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpRequestWriter.java $ + * $Revision: 569673 $ + * $Date: 2007-08-25 06:58:51 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.HttpMessage; +import org.apache.http.HttpRequest; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.message.LineFormatter; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpRequestWriter extends AbstractMessageWriter { + + public HttpRequestWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(buffer, formatter, params); + } + + protected void writeHeadLine(final HttpMessage message) + throws IOException { + + final CharArrayBuffer buffer = lineFormatter.formatRequestLine + (this.lineBuf, ((HttpRequest) message).getRequestLine()); + this.sessionBuffer.writeLine(buffer); + } + +} diff --git a/src/org/apache/http/impl/io/HttpResponseParser.java b/src/org/apache/http/impl/io/HttpResponseParser.java new file mode 100644 index 0000000..575aa18 --- /dev/null +++ b/src/org/apache/http/impl/io/HttpResponseParser.java @@ -0,0 +1,81 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpResponseParser.java $ + * $Revision: 589374 $ + * $Date: 2007-10-28 09:25:07 -0700 (Sun, 28 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpResponseFactory; +import org.apache.http.NoHttpResponseException; +import org.apache.http.StatusLine; +import org.apache.http.ParseException; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpResponseParser extends AbstractMessageParser { + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + + public HttpResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + if (responseFactory == null) { + throw new IllegalArgumentException("Response factory may not be null"); + } + this.responseFactory = responseFactory; + this.lineBuf = new CharArrayBuffer(128); + } + + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new NoHttpResponseException("The target server failed to respond"); + } + //create the status line from the status string + ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/src/org/apache/http/impl/io/HttpResponseWriter.java b/src/org/apache/http/impl/io/HttpResponseWriter.java new file mode 100644 index 0000000..f88791e --- /dev/null +++ b/src/org/apache/http/impl/io/HttpResponseWriter.java @@ -0,0 +1,59 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpResponseWriter.java $ + * $Revision: 569673 $ + * $Date: 2007-08-25 06:58:51 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.HttpMessage; +import org.apache.http.HttpResponse; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.message.LineFormatter; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpResponseWriter extends AbstractMessageWriter { + + public HttpResponseWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(buffer, formatter, params); + } + + protected void writeHeadLine(final HttpMessage message) + throws IOException { + + final CharArrayBuffer buffer = lineFormatter.formatStatusLine + (this.lineBuf, ((HttpResponse) message).getStatusLine()); + this.sessionBuffer.writeLine(buffer); + } + +} diff --git a/src/org/apache/http/impl/io/HttpTransportMetricsImpl.java b/src/org/apache/http/impl/io/HttpTransportMetricsImpl.java new file mode 100644 index 0000000..53e6772 --- /dev/null +++ b/src/org/apache/http/impl/io/HttpTransportMetricsImpl.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpTransportMetricsImpl.java $ + * $Revision: 539755 $ + * $Date: 2007-05-19 07:05:02 -0700 (Sat, 19 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import org.apache.http.io.HttpTransportMetrics; + +/** + * Default implementation of {@link HttpTransportMetrics}. + */ +public class HttpTransportMetricsImpl implements HttpTransportMetrics { + + private long bytesTransferred = 0; + + public HttpTransportMetricsImpl() { + super(); + } + + public long getBytesTransferred() { + return this.bytesTransferred; + } + + public void setBytesTransferred(long count) { + this.bytesTransferred = count; + } + + public void incrementBytesTransferred(long count) { + this.bytesTransferred += count; + } + + public void reset() { + this.bytesTransferred = 0; + } + +} diff --git a/src/org/apache/http/impl/io/IdentityInputStream.java b/src/org/apache/http/impl/io/IdentityInputStream.java new file mode 100644 index 0000000..390d5b7 --- /dev/null +++ b/src/org/apache/http/impl/io/IdentityInputStream.java @@ -0,0 +1,90 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/IdentityInputStream.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.io.SessionInputBuffer; + +/** + * A stream for reading from a {@link SessionInputBuffer session input buffer}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class IdentityInputStream extends InputStream { + + private final SessionInputBuffer in; + + private boolean closed = false; + + public IdentityInputStream(final SessionInputBuffer in) { + super(); + if (in == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + this.in = in; + } + + public int available() throws IOException { + if (!this.closed && this.in.isDataAvailable(10)) { + return 1; + } else { + return 0; + } + } + + public void close() throws IOException { + this.closed = true; + } + + public int read() throws IOException { + if (this.closed) { + return -1; + } else { + return this.in.read(); + } + } + + public int read(final byte[] b, int off, int len) throws IOException { + if (this.closed) { + return -1; + } else { + return this.in.read(b, off, len); + } + } + +} diff --git a/src/org/apache/http/impl/io/IdentityOutputStream.java b/src/org/apache/http/impl/io/IdentityOutputStream.java new file mode 100644 index 0000000..10b64f7 --- /dev/null +++ b/src/org/apache/http/impl/io/IdentityOutputStream.java @@ -0,0 +1,100 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/IdentityOutputStream.java $ + * $Revision: 560343 $ + * $Date: 2007-07-27 11:18:19 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; + +/** + * A stream for writing with an "identity" transport encoding. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560343 $ + * + * @since 4.0 + */ +public class IdentityOutputStream extends OutputStream { + + /** + * Wrapped session output buffer. + */ + private final SessionOutputBuffer out; + + /** True if the stream is closed. */ + private boolean closed = false; + + public IdentityOutputStream(final SessionOutputBuffer out) { + super(); + if (out == null) { + throw new IllegalArgumentException("Session output buffer may not be null"); + } + this.out = out; + } + + /** + * <p>Does not close the underlying socket output.</p> + * + * @throws IOException If an I/O problem occurs. + */ + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + this.out.flush(); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.out.write(b, off, len); + } + + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + public void write(int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.out.write(b); + } + +} diff --git a/src/org/apache/http/impl/io/SocketInputBuffer.java b/src/org/apache/http/impl/io/SocketInputBuffer.java new file mode 100644 index 0000000..925e80a --- /dev/null +++ b/src/org/apache/http/impl/io/SocketInputBuffer.java @@ -0,0 +1,115 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/SocketInputBuffer.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; + +import org.apache.http.params.HttpParams; + + +/** + * {@link Socket} bound session input buffer. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class SocketInputBuffer extends AbstractSessionInputBuffer { + + static private final Class SOCKET_TIMEOUT_CLASS = SocketTimeoutExceptionClass(); + + /** + * Returns <code>SocketTimeoutExceptionClass<code> or <code>null</code> if the class + * does not exist. + * + * @return <code>SocketTimeoutExceptionClass<code>, or <code>null</code> if unavailable. + */ + static private Class SocketTimeoutExceptionClass() { + try { + return Class.forName("java.net.SocketTimeoutException"); + } catch (ClassNotFoundException e) { + return null; + } + } + + private static boolean isSocketTimeoutException(final InterruptedIOException e) { + if (SOCKET_TIMEOUT_CLASS != null) { + return SOCKET_TIMEOUT_CLASS.isInstance(e); + } else { + return true; + } + } + + private final Socket socket; + + public SocketInputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + super(); + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + this.socket = socket; + if (buffersize < 0) { + buffersize = socket.getReceiveBufferSize(); + } + if (buffersize < 1024) { + buffersize = 1024; + } + init(socket.getInputStream(), buffersize, params); + } + + public boolean isDataAvailable(int timeout) throws IOException { + boolean result = hasBufferedData(); + if (!result) { + int oldtimeout = this.socket.getSoTimeout(); + try { + this.socket.setSoTimeout(timeout); + fillBuffer(); + result = hasBufferedData(); + } catch (InterruptedIOException e) { + if (!isSocketTimeoutException(e)) { + throw e; + } + } finally { + socket.setSoTimeout(oldtimeout); + } + } + return result; + } + +} diff --git a/src/org/apache/http/impl/io/SocketOutputBuffer.java b/src/org/apache/http/impl/io/SocketOutputBuffer.java new file mode 100644 index 0000000..efb91e9 --- /dev/null +++ b/src/org/apache/http/impl/io/SocketOutputBuffer.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/SocketOutputBuffer.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.http.params.HttpParams; + + +/** + * {@link Socket} bound session output buffer. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class SocketOutputBuffer extends AbstractSessionOutputBuffer { + + public SocketOutputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + super(); + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (buffersize < 0) { + buffersize = socket.getReceiveBufferSize(); +// BEGIN android-changed + // Workaround for http://b/issue?id=1083103. + if (buffersize > 8096) { + buffersize = 8096; + } +// END android-changed + } + if (buffersize < 1024) { + buffersize = 1024; + } + +// BEGIN android-changed + socket.setSendBufferSize(buffersize * 3); +// END andrdoid-changed + + init(socket.getOutputStream(), buffersize, params); + } + +} diff --git a/src/org/apache/http/impl/io/package.html b/src/org/apache/http/impl/io/package.html new file mode 100644 index 0000000..48eb2c1 --- /dev/null +++ b/src/org/apache/http/impl/io/package.html @@ -0,0 +1,48 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/package.html $ + * $Revision: 567360 $ + * $Date: 2007-08-18 23:49:21 -0700 (Sat, 18 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Default implementations for interfaces in +{@link org.apache.http.io org.apache.http.io}. + +<br/> + +There are implementations of the transport encodings used by HTTP, +in particular the chunked encoding for +{@link org.apache.http.impl.io.ChunkedOutputStream sending} and +{@link org.apache.http.impl.io.ChunkedInputStream receiving} entities. + +</body> +</html> diff --git a/src/org/apache/http/impl/package.html b/src/org/apache/http/impl/package.html new file mode 100644 index 0000000..6cec586 --- /dev/null +++ b/src/org/apache/http/impl/package.html @@ -0,0 +1,41 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/package.html $ + * $Revision: 496072 $ + * $Date: 2007-01-14 04:22:55 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Default implementations for interfaces in +{@link org.apache.http org.apache.http}. + +</body> +</html> diff --git a/src/org/apache/http/io/HttpMessageParser.java b/src/org/apache/http/io/HttpMessageParser.java new file mode 100644 index 0000000..5c24736 --- /dev/null +++ b/src/org/apache/http/io/HttpMessageParser.java @@ -0,0 +1,53 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/io/HttpMessageParser.java $ + * $Revision: 567370 $ + * $Date: 2007-08-19 01:13:21 -0700 (Sun, 19 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.io; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; + +/** + * Generic message parser interface. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 567370 $ + * + * @since 4.0 + */ +public interface HttpMessageParser { + + HttpMessage parse() + throws IOException, HttpException; + +} diff --git a/src/org/apache/http/io/HttpMessageWriter.java b/src/org/apache/http/io/HttpMessageWriter.java new file mode 100644 index 0000000..b6ac7c1 --- /dev/null +++ b/src/org/apache/http/io/HttpMessageWriter.java @@ -0,0 +1,53 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/io/HttpMessageWriter.java $ + * $Revision: 567370 $ + * $Date: 2007-08-19 01:13:21 -0700 (Sun, 19 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.io; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; + +/** + * Generic message writer interface. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 567370 $ + * + * @since 4.0 + */ +public interface HttpMessageWriter { + + void write(HttpMessage message) + throws IOException, HttpException; + +} diff --git a/src/org/apache/http/io/HttpTransportMetrics.java b/src/org/apache/http/io/HttpTransportMetrics.java new file mode 100644 index 0000000..f88e036 --- /dev/null +++ b/src/org/apache/http/io/HttpTransportMetrics.java @@ -0,0 +1,46 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/io/HttpTransportMetrics.java $ + * $Revision: 536667 $ + * $Date: 2007-05-09 14:48:41 -0700 (Wed, 09 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.io; + +public interface HttpTransportMetrics { + + /** + * Returns the number of bytes trasferred. + */ + long getBytesTransferred(); + + /** + * Resets the counts + */ + void reset(); + +} diff --git a/src/org/apache/http/io/SessionInputBuffer.java b/src/org/apache/http/io/SessionInputBuffer.java new file mode 100644 index 0000000..d7824d9 --- /dev/null +++ b/src/org/apache/http/io/SessionInputBuffer.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/io/SessionInputBuffer.java $ + * $Revision: 560528 $ + * $Date: 2007-07-28 04:34:17 -0700 (Sat, 28 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.io; + +import java.io.IOException; + +import org.apache.http.util.CharArrayBuffer; + +/** + * Session input buffer for blocking connections. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560528 $ + * + * @since 4.0 + */ +public interface SessionInputBuffer { + + int read(byte[] b, int off, int len) throws IOException; + + int read(byte[] b) throws IOException; + + int read() throws IOException; + + int readLine(CharArrayBuffer buffer) throws IOException; + + String readLine() throws IOException; + + boolean isDataAvailable(int timeout) throws IOException; + + HttpTransportMetrics getMetrics(); + +} diff --git a/src/org/apache/http/io/SessionOutputBuffer.java b/src/org/apache/http/io/SessionOutputBuffer.java new file mode 100644 index 0000000..6587a26 --- /dev/null +++ b/src/org/apache/http/io/SessionOutputBuffer.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/io/SessionOutputBuffer.java $ + * $Revision: 560528 $ + * $Date: 2007-07-28 04:34:17 -0700 (Sat, 28 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.io; + +import java.io.IOException; + +import org.apache.http.util.CharArrayBuffer; + +/** + * Session output buffer for blocking connections. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560528 $ + * + * @since 4.0 + */ +public interface SessionOutputBuffer { + + void write(byte[] b, int off, int len) throws IOException; + + void write(byte[] b) throws IOException; + + void write(int b) throws IOException; + + void writeLine(String s) throws IOException; + + void writeLine(CharArrayBuffer buffer) throws IOException; + + void flush() throws IOException; + + HttpTransportMetrics getMetrics(); + +} diff --git a/src/org/apache/http/io/package.html b/src/org/apache/http/io/package.html new file mode 100644 index 0000000..da9fabf --- /dev/null +++ b/src/org/apache/http/io/package.html @@ -0,0 +1,47 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/io/package.html $ + * $Revision: 503192 $ + * $Date: 2007-02-03 03:55:49 -0800 (Sat, 03 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The transport layer abstraction of the HTTP components. + +This layer is used to transfer messages over connections. +Connections are typically based on sockets: plain or secure, +direct or via SOCKS hosts, with bandwidth throttling, or +whatever else you might think of. +However, opening connections is not within the responsibility +of HttpCore. + +</body> +</html> diff --git a/src/org/apache/http/message/AbstractHttpMessage.java b/src/org/apache/http/message/AbstractHttpMessage.java new file mode 100644 index 0000000..d8a6962 --- /dev/null +++ b/src/org/apache/http/message/AbstractHttpMessage.java @@ -0,0 +1,166 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/AbstractHttpMessage.java $ + * $Revision: 620287 $ + * $Date: 2008-02-10 07:15:53 -0800 (Sun, 10 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import java.util.Iterator; + +import org.apache.http.Header; +import org.apache.http.HeaderIterator; +import org.apache.http.HttpMessage; +import org.apache.http.params.HttpParams; +import org.apache.http.params.BasicHttpParams; + +/** + * Basic implementation of an HTTP message that can be modified. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 620287 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpMessage implements HttpMessage { + + protected HeaderGroup headergroup; + + protected HttpParams params; + + protected AbstractHttpMessage(final HttpParams params) { + super(); + this.headergroup = new HeaderGroup(); + this.params = params; + } + + protected AbstractHttpMessage() { + this(null); + } + + // non-javadoc, see interface HttpMessage + public boolean containsHeader(String name) { + return this.headergroup.containsHeader(name); + } + + // non-javadoc, see interface HttpMessage + public Header[] getHeaders(final String name) { + return this.headergroup.getHeaders(name); + } + + // non-javadoc, see interface HttpMessage + public Header getFirstHeader(final String name) { + return this.headergroup.getFirstHeader(name); + } + + // non-javadoc, see interface HttpMessage + public Header getLastHeader(final String name) { + return this.headergroup.getLastHeader(name); + } + + // non-javadoc, see interface HttpMessage + public Header[] getAllHeaders() { + return this.headergroup.getAllHeaders(); + } + + // non-javadoc, see interface HttpMessage + public void addHeader(final Header header) { + this.headergroup.addHeader(header); + } + + // non-javadoc, see interface HttpMessage + public void addHeader(final String name, final String value) { + if (name == null) { + throw new IllegalArgumentException("Header name may not be null"); + } + this.headergroup.addHeader(new BasicHeader(name, value)); + } + + // non-javadoc, see interface HttpMessage + public void setHeader(final Header header) { + this.headergroup.updateHeader(header); + } + + // non-javadoc, see interface HttpMessage + public void setHeader(final String name, final String value) { + if (name == null) { + throw new IllegalArgumentException("Header name may not be null"); + } + this.headergroup.updateHeader(new BasicHeader(name, value)); + } + + // non-javadoc, see interface HttpMessage + public void setHeaders(final Header[] headers) { + this.headergroup.setHeaders(headers); + } + + // non-javadoc, see interface HttpMessage + public void removeHeader(final Header header) { + this.headergroup.removeHeader(header); + } + + // non-javadoc, see interface HttpMessage + public void removeHeaders(final String name) { + if (name == null) { + return; + } + for (Iterator i = this.headergroup.iterator(); i.hasNext(); ) { + Header header = (Header) i.next(); + if (name.equalsIgnoreCase(header.getName())) { + i.remove(); + } + } + } + + // non-javadoc, see interface HttpMessage + public HeaderIterator headerIterator() { + return this.headergroup.iterator(); + } + + // non-javadoc, see interface HttpMessage + public HeaderIterator headerIterator(String name) { + return this.headergroup.iterator(name); + } + + // non-javadoc, see interface HttpMessage + public HttpParams getParams() { + if (this.params == null) { + this.params = new BasicHttpParams(); + } + return this.params; + } + + // non-javadoc, see interface HttpMessage + public void setParams(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.params = params; + } +} diff --git a/src/org/apache/http/message/BasicHeader.java b/src/org/apache/http/message/BasicHeader.java new file mode 100644 index 0000000..f134d8d --- /dev/null +++ b/src/org/apache/http/message/BasicHeader.java @@ -0,0 +1,143 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeader.java $ + * $Revision: 652956 $ + * $Date: 2008-05-02 17:13:05 -0700 (Fri, 02 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.ParseException; + +/** + * Represents an HTTP header field. + * + * <p>The HTTP header fields follow the same generic format as + * that given in Section 3.1 of RFC 822. Each header field consists + * of a name followed by a colon (":") and the field value. Field names + * are case-insensitive. The field value MAY be preceded by any amount + * of LWS, though a single SP is preferred. + * + *<pre> + * message-header = field-name ":" [ field-value ] + * field-name = token + * field-value = *( field-content | LWS ) + * field-content = <the OCTETs making up the field-value + * and consisting of either *TEXT or combinations + * of token, separators, and quoted-string> + *</pre> + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 652956 $ $Date: 2008-05-02 17:13:05 -0700 (Fri, 02 May 2008) $ + * + * @since 4.0 + */ +public class BasicHeader implements Header, Cloneable { + + /** + * Header name. + */ + private final String name; + + /** + * Header value. + */ + private final String value; + + /** + * Constructor with name and value + * + * @param name the header name + * @param value the header value + */ + public BasicHeader(final String name, final String value) { + super(); + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + this.name = name; + this.value = value; + } + + /** + * Returns the header name. + * + * @return String name The name + */ + public String getName() { + return this.name; + } + + /** + * Returns the header value. + * + * @return String value The current value. + */ + public String getValue() { + return this.value; + } + + /** + * Returns a {@link String} representation of the header. + * + * @return a string + */ + public String toString() { + // no need for non-default formatting in toString() + return BasicLineFormatter.DEFAULT.formatHeader(null, this).toString(); + } + + /** + * Returns an array of {@link HeaderElement}s constructed from my value. + * + * @see BasicHeaderValueParser#parseElements(String, HeaderValueParser) + * + * @return an array of header elements + * + * @throws ParseException in case of a parse error + */ + public HeaderElement[] getElements() throws ParseException { + if (this.value != null) { + // result intentionally not cached, it's probably not used again + return BasicHeaderValueParser.parseElements(this.value, null); + } else { + return new HeaderElement[] {}; + } + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/org/apache/http/message/BasicHeaderElement.java b/src/org/apache/http/message/BasicHeaderElement.java new file mode 100644 index 0000000..19a40c6 --- /dev/null +++ b/src/org/apache/http/message/BasicHeaderElement.java @@ -0,0 +1,243 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeaderElement.java $ + * $Revision: 604625 $ + * $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.LangUtils; + +/** + * One element of an HTTP header's value. + * <p> + * Some HTTP headers (such as the set-cookie header) have values that + * can be decomposed into multiple elements. Such headers must be in the + * following form: + * </p> + * <pre> + * header = [ element ] *( "," [ element ] ) + * element = name [ "=" [ value ] ] *( ";" [ param ] ) + * param = name [ "=" [ value ] ] + * + * name = token + * value = ( token | quoted-string ) + * + * token = 1*<any char except "=", ",", ";", <"> and + * white space> + * quoted-string = <"> *( text | quoted-char ) <"> + * text = any char except <"> + * quoted-char = "\" char + * </pre> + * <p> + * Any amount of white space is allowed between any part of the + * header, element or param and is ignored. A missing value in any + * element or param will be stored as the empty {@link String}; + * if the "=" is also missing <var>null</var> will be stored instead. + * </p> + * <p> + * This class represents an individual header element, containing + * both a name/value pair (value may be <tt>null</tt>) and optionally + * a set of additional parameters. + * </p> + * + * @author <a href="mailto:bcholmes@interlog.com">B.C. Holmes</a> + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 604625 $ $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * @since 4.0 + */ +public class BasicHeaderElement implements HeaderElement, Cloneable { + + private final String name; + private final String value; + private final NameValuePair[] parameters; + + /** + * Constructor with name, value and parameters. + * + * @param name header element name + * @param value header element value. May be <tt>null</tt> + * @param parameters header element parameters. May be <tt>null</tt>. + * Parameters are copied by reference, not by value + */ + public BasicHeaderElement( + final String name, + final String value, + final NameValuePair[] parameters) { + super(); + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + this.name = name; + this.value = value; + if (parameters != null) { + this.parameters = parameters; + } else { + this.parameters = new NameValuePair[] {}; + } + } + + /** + * Constructor with name and value. + * + * @param name header element name + * @param value header element value. May be <tt>null</tt> + */ + public BasicHeaderElement(final String name, final String value) { + this(name, value, null); + } + + /** + * Returns the name. + * + * @return String name The name + */ + public String getName() { + return this.name; + } + + /** + * Returns the value. + * + * @return String value The current value. + */ + public String getValue() { + return this.value; + } + + /** + * Get parameters, if any. + * The returned array is created for each invocation and can + * be modified by the caller without affecting this header element. + * + * @return parameters as an array of {@link NameValuePair}s + */ + public NameValuePair[] getParameters() { + return (NameValuePair[])this.parameters.clone(); + } + + + /** + * Obtains the number of parameters. + * + * @return the number of parameters + */ + public int getParameterCount() { + return this.parameters.length; + } + + + /** + * Obtains the parameter with the given index. + * + * @param index the index of the parameter, 0-based + * + * @return the parameter with the given index + */ + public NameValuePair getParameter(int index) { + // ArrayIndexOutOfBoundsException is appropriate + return this.parameters[index]; + } + + + /** + * Returns parameter with the given name, if found. Otherwise null + * is returned + * + * @param name The name to search by. + * @return NameValuePair parameter with the given name + */ + public NameValuePair getParameterByName(final String name) { + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + NameValuePair found = null; + for (int i = 0; i < this.parameters.length; i++) { + NameValuePair current = this.parameters[ i ]; + if (current.getName().equalsIgnoreCase(name)) { + found = current; + break; + } + } + return found; + } + + public boolean equals(final Object object) { + if (object == null) return false; + if (this == object) return true; + if (object instanceof HeaderElement) { + BasicHeaderElement that = (BasicHeaderElement) object; + return this.name.equals(that.name) + && LangUtils.equals(this.value, that.value) + && LangUtils.equals(this.parameters, that.parameters); + } else { + return false; + } + } + + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.name); + hash = LangUtils.hashCode(hash, this.value); + for (int i = 0; i < this.parameters.length; i++) { + hash = LangUtils.hashCode(hash, this.parameters[i]); + } + return hash; + } + + public String toString() { + CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append(this.name); + if (this.value != null) { + buffer.append("="); + buffer.append(this.value); + } + for (int i = 0; i < this.parameters.length; i++) { + buffer.append("; "); + buffer.append(this.parameters[i]); + } + return buffer.toString(); + } + + public Object clone() throws CloneNotSupportedException { + // parameters array is considered immutable + // no need to make a copy of it + return super.clone(); + } + +} + diff --git a/src/org/apache/http/message/BasicHeaderElementIterator.java b/src/org/apache/http/message/BasicHeaderElementIterator.java new file mode 100644 index 0000000..46f53a8 --- /dev/null +++ b/src/org/apache/http/message/BasicHeaderElementIterator.java @@ -0,0 +1,161 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeaderElementIterator.java $ + * $Revision: 592088 $ + * $Date: 2007-11-05 09:03:39 -0800 (Mon, 05 Nov 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import java.util.NoSuchElementException; + +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.HeaderIterator; +import org.apache.http.util.CharArrayBuffer; + +/** + * Basic implementation of a {@link HeaderElementIterator}. + * + * @version $Revision: 592088 $ + * + * @author Andrea Selva <selva.andre at gmail.com> + * @author Oleg Kalnichevski <oleg at ural.ru> + */ +public class BasicHeaderElementIterator implements HeaderElementIterator { + + private final HeaderIterator headerIt; + private final HeaderValueParser parser; + + private HeaderElement currentElement = null; + private CharArrayBuffer buffer = null; + private ParserCursor cursor = null; + + /** + * Creates a new instance of BasicHeaderElementIterator + */ + public BasicHeaderElementIterator( + final HeaderIterator headerIterator, + final HeaderValueParser parser) { + if (headerIterator == null) { + throw new IllegalArgumentException("Header iterator may not be null"); + } + if (parser == null) { + throw new IllegalArgumentException("Parser may not be null"); + } + this.headerIt = headerIterator; + this.parser = parser; + } + + + public BasicHeaderElementIterator(final HeaderIterator headerIterator) { + this(headerIterator, BasicHeaderValueParser.DEFAULT); + } + + + private void bufferHeaderValue() { + this.cursor = null; + this.buffer = null; + while (this.headerIt.hasNext()) { + Header h = this.headerIt.nextHeader(); + if (h instanceof FormattedHeader) { + this.buffer = ((FormattedHeader) h).getBuffer(); + this.cursor = new ParserCursor(0, this.buffer.length()); + this.cursor.updatePos(((FormattedHeader) h).getValuePos()); + break; + } else { + String value = h.getValue(); + if (value != null) { + this.buffer = new CharArrayBuffer(value.length()); + this.buffer.append(value); + this.cursor = new ParserCursor(0, this.buffer.length()); + break; + } + } + } + } + + private void parseNextElement() { + // loop while there are headers left to parse + while (this.headerIt.hasNext() || this.cursor != null) { + if (this.cursor == null || this.cursor.atEnd()) { + // get next header value + bufferHeaderValue(); + } + // Anything buffered? + if (this.cursor != null) { + // loop while there is data in the buffer + while (!this.cursor.atEnd()) { + HeaderElement e = this.parser.parseHeaderElement(this.buffer, this.cursor); + if (!(e.getName().length() == 0 && e.getValue() == null)) { + // Found something + this.currentElement = e; + return; + } + } + // if at the end of the buffer + if (this.cursor.atEnd()) { + // discard it + this.cursor = null; + this.buffer = null; + } + } + } + } + + public boolean hasNext() { + if (this.currentElement == null) { + parseNextElement(); + } + return this.currentElement != null; + } + + public HeaderElement nextElement() throws NoSuchElementException { + if (this.currentElement == null) { + parseNextElement(); + } + + if (this.currentElement == null) { + throw new NoSuchElementException("No more header elements available"); + } + + HeaderElement element = this.currentElement; + this.currentElement = null; + return element; + } + + public final Object next() throws NoSuchElementException { + return nextElement(); + } + + public void remove() throws UnsupportedOperationException { + throw new UnsupportedOperationException("Remove not supported"); + } + +}
\ No newline at end of file diff --git a/src/org/apache/http/message/BasicHeaderIterator.java b/src/org/apache/http/message/BasicHeaderIterator.java new file mode 100644 index 0000000..32cd1c8 --- /dev/null +++ b/src/org/apache/http/message/BasicHeaderIterator.java @@ -0,0 +1,180 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeaderIterator.java $ + * $Revision: 581981 $ + * $Date: 2007-10-04 11:26:26 -0700 (Thu, 04 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import java.util.NoSuchElementException; + +import org.apache.http.Header; +import org.apache.http.HeaderIterator; + + +/** + * Basic implementation of a {@link HeaderIterator}. + * + * @version $Revision: 581981 $ + */ +public class BasicHeaderIterator implements HeaderIterator { + + /** + * An array of headers to iterate over. + * Not all elements of this array are necessarily part of the iteration. + * This array will never be modified by the iterator. + * Derived implementations are expected to adhere to this restriction. + */ + protected final Header[] allHeaders; + + + /** + * The position of the next header in {@link #allHeaders allHeaders}. + * Negative if the iteration is over. + */ + protected int currentIndex; + + + /** + * The header name to filter by. + * <code>null</code> to iterate over all headers in the array. + */ + protected String headerName; + + + + /** + * Creates a new header iterator. + * + * @param headers an array of headers over which to iterate + * @param name the name of the headers over which to iterate, or + * <code>null</code> for any + */ + public BasicHeaderIterator(Header[] headers, String name) { + if (headers == null) { + throw new IllegalArgumentException + ("Header array must not be null."); + } + + this.allHeaders = headers; + this.headerName = name; + this.currentIndex = findNext(-1); + } + + + /** + * Determines the index of the next header. + * + * @param from one less than the index to consider first, + * -1 to search for the first header + * + * @return the index of the next header that matches the filter name, + * or negative if there are no more headers + */ + protected int findNext(int from) { + if (from < -1) + return -1; + + final int to = this.allHeaders.length-1; + boolean found = false; + while (!found && (from < to)) { + from++; + found = filterHeader(from); + } + return found ? from : -1; + } + + + /** + * Checks whether a header is part of the iteration. + * + * @param index the index of the header to check + * + * @return <code>true</code> if the header should be part of the + * iteration, <code>false</code> to skip + */ + protected boolean filterHeader(int index) { + return (this.headerName == null) || + this.headerName.equalsIgnoreCase(this.allHeaders[index].getName()); + } + + + // non-javadoc, see interface HeaderIterator + public boolean hasNext() { + return (this.currentIndex >= 0); + } + + + /** + * Obtains the next header from this iteration. + * + * @return the next header in this iteration + * + * @throws NoSuchElementException if there are no more headers + */ + public Header nextHeader() + throws NoSuchElementException { + + final int current = this.currentIndex; + if (current < 0) { + throw new NoSuchElementException("Iteration already finished."); + } + + this.currentIndex = findNext(current); + + return this.allHeaders[current]; + } + + + /** + * Returns the next header. + * Same as {@link #nextHeader nextHeader}, but not type-safe. + * + * @return the next header in this iteration + * + * @throws NoSuchElementException if there are no more headers + */ + public final Object next() + throws NoSuchElementException { + return nextHeader(); + } + + + /** + * Removing headers is not supported. + * + * @throws UnsupportedOperationException always + */ + public void remove() + throws UnsupportedOperationException { + + throw new UnsupportedOperationException + ("Removing headers is not supported."); + } +} diff --git a/src/org/apache/http/message/BasicHeaderValueFormatter.java b/src/org/apache/http/message/BasicHeaderValueFormatter.java new file mode 100644 index 0000000..b63bdf7 --- /dev/null +++ b/src/org/apache/http/message/BasicHeaderValueFormatter.java @@ -0,0 +1,441 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeaderValueFormatter.java $ + * $Revision: 574185 $ + * $Date: 2007-09-10 02:19:47 -0700 (Mon, 10 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.util.CharArrayBuffer; + + +/** + * Basic implementation for formatting header value elements. + * Instances of this class are stateless and thread-safe. + * Derived classes are expected to maintain these properties. + * + * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a> + * @author and others + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 574185 $ + * + * @since 4.0 + */ +public class BasicHeaderValueFormatter implements HeaderValueFormatter { + + /** + * A default instance of this class, for use as default or fallback. + * Note that {@link BasicHeaderValueFormatter} is not a singleton, there + * can be many instances of the class itself and of derived classes. + * The instance here provides non-customized, default behavior. + */ + public final static + BasicHeaderValueFormatter DEFAULT = new BasicHeaderValueFormatter(); + + + /** + * Special characters that can be used as separators in HTTP parameters. + * These special characters MUST be in a quoted string to be used within + * a parameter value . + */ + public final static String SEPARATORS = " ;,:@()<>\\\"/[]?={}\t"; + + + /** + * Unsafe special characters that must be escaped using the backslash + * character + */ + public final static String UNSAFE_CHARS = "\"\\"; + + + + // public default constructor + + + + /** + * Formats an array of header elements. + * + * @param elems the header elements to format + * @param quote <code>true</code> to always format with quoted values, + * <code>false</code> to use quotes only when necessary + * @param formatter the formatter to use, or <code>null</code> + * for the {@link #DEFAULT default} + * + * @return the formatted header elements + */ + public final static + String formatElements(final HeaderElement[] elems, + final boolean quote, + HeaderValueFormatter formatter) { + if (formatter == null) + formatter = BasicHeaderValueFormatter.DEFAULT; + return formatter.formatElements(null, elems, quote).toString(); + } + + + // non-javadoc, see interface HeaderValueFormatter + public CharArrayBuffer formatElements(CharArrayBuffer buffer, + final HeaderElement[] elems, + final boolean quote) { + if (elems == null) { + throw new IllegalArgumentException + ("Header element array must not be null."); + } + + int len = estimateElementsLen(elems); + if (buffer == null) { + buffer = new CharArrayBuffer(len); + } else { + buffer.ensureCapacity(len); + } + + for (int i=0; i<elems.length; i++) { + if (i > 0) { + buffer.append(", "); + } + formatHeaderElement(buffer, elems[i], quote); + } + + return buffer; + } + + + /** + * Estimates the length of formatted header elements. + * + * @param elems the header elements to format, or <code>null</code> + * + * @return a length estimate, in number of characters + */ + protected int estimateElementsLen(final HeaderElement[] elems) { + if ((elems == null) || (elems.length < 1)) + return 0; + + int result = (elems.length-1) * 2; // elements separated by ", " + for (int i=0; i<elems.length; i++) { + result += estimateHeaderElementLen(elems[i]); + } + + return result; + } + + + + /** + * Formats a header element. + * + * @param elem the header element to format + * @param quote <code>true</code> to always format with quoted values, + * <code>false</code> to use quotes only when necessary + * @param formatter the formatter to use, or <code>null</code> + * for the {@link #DEFAULT default} + * + * @return the formatted header element + */ + public final static + String formatHeaderElement(final HeaderElement elem, + boolean quote, + HeaderValueFormatter formatter) { + if (formatter == null) + formatter = BasicHeaderValueFormatter.DEFAULT; + return formatter.formatHeaderElement(null, elem, quote).toString(); + } + + + // non-javadoc, see interface HeaderValueFormatter + public CharArrayBuffer formatHeaderElement(CharArrayBuffer buffer, + final HeaderElement elem, + final boolean quote) { + if (elem == null) { + throw new IllegalArgumentException + ("Header element must not be null."); + } + + int len = estimateHeaderElementLen(elem); + if (buffer == null) { + buffer = new CharArrayBuffer(len); + } else { + buffer.ensureCapacity(len); + } + + buffer.append(elem.getName()); + final String value = elem.getValue(); + if (value != null) { + buffer.append('='); + doFormatValue(buffer, value, quote); + } + + final int parcnt = elem.getParameterCount(); + if (parcnt > 0) { + for (int i=0; i<parcnt; i++) { + buffer.append("; "); + formatNameValuePair(buffer, elem.getParameter(i), quote); + } + } + + return buffer; + } + + + /** + * Estimates the length of a formatted header element. + * + * @param elem the header element to format, or <code>null</code> + * + * @return a length estimate, in number of characters + */ + protected int estimateHeaderElementLen(final HeaderElement elem) { + if (elem == null) + return 0; + + int result = elem.getName().length(); // name + final String value = elem.getValue(); + if (value != null) { + // assume quotes, but no escaped characters + result += 3 + value.length(); // ="value" + } + + final int parcnt = elem.getParameterCount(); + if (parcnt > 0) { + for (int i=0; i<parcnt; i++) { + result += 2 + // ; <param> + estimateNameValuePairLen(elem.getParameter(i)); + } + } + + return result; + } + + + + + /** + * Formats a set of parameters. + * + * @param nvps the parameters to format + * @param quote <code>true</code> to always format with quoted values, + * <code>false</code> to use quotes only when necessary + * @param formatter the formatter to use, or <code>null</code> + * for the {@link #DEFAULT default} + * + * @return the formatted parameters + */ + public final static + String formatParameters(final NameValuePair[] nvps, + final boolean quote, + HeaderValueFormatter formatter) { + if (formatter == null) + formatter = BasicHeaderValueFormatter.DEFAULT; + return formatter.formatParameters(null, nvps, quote).toString(); + } + + + // non-javadoc, see interface HeaderValueFormatter + public CharArrayBuffer formatParameters(CharArrayBuffer buffer, + NameValuePair[] nvps, + boolean quote) { + if (nvps == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + int len = estimateParametersLen(nvps); + if (buffer == null) { + buffer = new CharArrayBuffer(len); + } else { + buffer.ensureCapacity(len); + } + + for (int i = 0; i < nvps.length; i++) { + if (i > 0) { + buffer.append("; "); + } + formatNameValuePair(buffer, nvps[i], quote); + } + + return buffer; + } + + + /** + * Estimates the length of formatted parameters. + * + * @param nvps the parameters to format, or <code>null</code> + * + * @return a length estimate, in number of characters + */ + protected int estimateParametersLen(final NameValuePair[] nvps) { + if ((nvps == null) || (nvps.length < 1)) + return 0; + + int result = (nvps.length-1) * 2; // "; " between the parameters + for (int i=0; i<nvps.length; i++) { + result += estimateNameValuePairLen(nvps[i]); + } + + return result; + } + + + /** + * Formats a name-value pair. + * + * @param nvp the name-value pair to format + * @param quote <code>true</code> to always format with a quoted value, + * <code>false</code> to use quotes only when necessary + * @param formatter the formatter to use, or <code>null</code> + * for the {@link #DEFAULT default} + * + * @return the formatted name-value pair + */ + public final static + String formatNameValuePair(final NameValuePair nvp, + final boolean quote, + HeaderValueFormatter formatter) { + if (formatter == null) + formatter = BasicHeaderValueFormatter.DEFAULT; + return formatter.formatNameValuePair(null, nvp, quote).toString(); + } + + + // non-javadoc, see interface HeaderValueFormatter + public CharArrayBuffer formatNameValuePair(CharArrayBuffer buffer, + final NameValuePair nvp, + final boolean quote) { + if (nvp == null) { + throw new IllegalArgumentException + ("NameValuePair must not be null."); + } + + int len = estimateNameValuePairLen(nvp); + if (buffer == null) { + buffer = new CharArrayBuffer(len); + } else { + buffer.ensureCapacity(len); + } + + buffer.append(nvp.getName()); + final String value = nvp.getValue(); + if (value != null) { + buffer.append('='); + doFormatValue(buffer, value, quote); + } + + return buffer; + } + + + /** + * Estimates the length of a formatted name-value pair. + * + * @param nvp the name-value pair to format, or <code>null</code> + * + * @return a length estimate, in number of characters + */ + protected int estimateNameValuePairLen(final NameValuePair nvp) { + if (nvp == null) + return 0; + + int result = nvp.getName().length(); // name + final String value = nvp.getValue(); + if (value != null) { + // assume quotes, but no escaped characters + result += 3 + value.length(); // ="value" + } + return result; + } + + + /** + * Actually formats the value of a name-value pair. + * This does not include a leading = character. + * Called from {@link #formatNameValuePair formatNameValuePair}. + * + * @param buffer the buffer to append to, never <code>null</code> + * @param value the value to append, never <code>null</code> + * @param quote <code>true</code> to always format with quotes, + * <code>false</code> to use quotes only when necessary + */ + protected void doFormatValue(final CharArrayBuffer buffer, + final String value, + boolean quote) { + + if (!quote) { + for (int i = 0; (i < value.length()) && !quote; i++) { + quote = isSeparator(value.charAt(i)); + } + } + + if (quote) { + buffer.append('"'); + } + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (isUnsafe(ch)) { + buffer.append('\\'); + } + buffer.append(ch); + } + if (quote) { + buffer.append('"'); + } + } + + + /** + * Checks whether a character is a {@link #SEPARATORS separator}. + * + * @param ch the character to check + * + * @return <code>true</code> if the character is a separator, + * <code>false</code> otherwise + */ + protected boolean isSeparator(char ch) { + return SEPARATORS.indexOf(ch) >= 0; + } + + + /** + * Checks whether a character is {@link #UNSAFE_CHARS unsafe}. + * + * @param ch the character to check + * + * @return <code>true</code> if the character is unsafe, + * <code>false</code> otherwise + */ + protected boolean isUnsafe(char ch) { + return UNSAFE_CHARS.indexOf(ch) >= 0; + } + + +} // class BasicHeaderValueFormatter diff --git a/src/org/apache/http/message/BasicHeaderValueParser.java b/src/org/apache/http/message/BasicHeaderValueParser.java new file mode 100644 index 0000000..5216196 --- /dev/null +++ b/src/org/apache/http/message/BasicHeaderValueParser.java @@ -0,0 +1,420 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHeaderValueParser.java $ + * $Revision: 595670 $ + * $Date: 2007-11-16 06:15:01 -0800 (Fri, 16 Nov 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import java.util.List; +import java.util.ArrayList; + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.CharArrayBuffer; + + + +/** + * Basic implementation for parsing header values into elements. + * Instances of this class are stateless and thread-safe. + * Derived classes are expected to maintain these properties. + * + * @author <a href="mailto:bcholmes@interlog.com">B.C. Holmes</a> + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a> + * @author and others + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 595670 $ + * + * @since 4.0 + */ +public class BasicHeaderValueParser implements HeaderValueParser { + + /** + * A default instance of this class, for use as default or fallback. + * Note that {@link BasicHeaderValueParser} is not a singleton, there + * can be many instances of the class itself and of derived classes. + * The instance here provides non-customized, default behavior. + */ + public final static + BasicHeaderValueParser DEFAULT = new BasicHeaderValueParser(); + + private final static char PARAM_DELIMITER = ';'; + private final static char ELEM_DELIMITER = ','; + private final static char[] ALL_DELIMITERS = new char[] { + PARAM_DELIMITER, + ELEM_DELIMITER + }; + + // public default constructor + + + /** + * Parses elements with the given parser. + * + * @param value the header value to parse + * @param parser the parser to use, or <code>null</code> for default + * + * @return array holding the header elements, never <code>null</code> + */ + public final static + HeaderElement[] parseElements(final String value, + HeaderValueParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null"); + } + + if (parser == null) + parser = BasicHeaderValueParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + ParserCursor cursor = new ParserCursor(0, value.length()); + return parser.parseElements(buffer, cursor); + } + + + // non-javadoc, see interface HeaderValueParser + public HeaderElement[] parseElements(final CharArrayBuffer buffer, + final ParserCursor cursor) { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + + List elements = new ArrayList(); + while (!cursor.atEnd()) { + HeaderElement element = parseHeaderElement(buffer, cursor); + if (!(element.getName().length() == 0 && element.getValue() == null)) { + elements.add(element); + } + } + return (HeaderElement[]) + elements.toArray(new HeaderElement[elements.size()]); + } + + + /** + * Parses an element with the given parser. + * + * @param value the header element to parse + * @param parser the parser to use, or <code>null</code> for default + * + * @return the parsed header element + */ + public final static + HeaderElement parseHeaderElement(final String value, + HeaderValueParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null"); + } + + if (parser == null) + parser = BasicHeaderValueParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + ParserCursor cursor = new ParserCursor(0, value.length()); + return parser.parseHeaderElement(buffer, cursor); + } + + + // non-javadoc, see interface HeaderValueParser + public HeaderElement parseHeaderElement(final CharArrayBuffer buffer, + final ParserCursor cursor) { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + + NameValuePair nvp = parseNameValuePair(buffer, cursor); + NameValuePair[] params = null; + if (!cursor.atEnd()) { + char ch = buffer.charAt(cursor.getPos() - 1); + if (ch != ELEM_DELIMITER) { + params = parseParameters(buffer, cursor); + } + } + return createHeaderElement(nvp.getName(), nvp.getValue(), params); + } + + + /** + * Creates a header element. + * Called from {@link #parseHeaderElement}. + * + * @return a header element representing the argument + */ + protected HeaderElement createHeaderElement( + final String name, + final String value, + final NameValuePair[] params) { + return new BasicHeaderElement(name, value, params); + } + + + /** + * Parses parameters with the given parser. + * + * @param value the parameter list to parse + * @param parser the parser to use, or <code>null</code> for default + * + * @return array holding the parameters, never <code>null</code> + */ + public final static + NameValuePair[] parseParameters(final String value, + HeaderValueParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null"); + } + + if (parser == null) + parser = BasicHeaderValueParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + ParserCursor cursor = new ParserCursor(0, value.length()); + return parser.parseParameters(buffer, cursor); + } + + + + // non-javadoc, see interface HeaderValueParser + public NameValuePair[] parseParameters(final CharArrayBuffer buffer, + final ParserCursor cursor) { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + + int pos = cursor.getPos(); + int indexTo = cursor.getUpperBound(); + + while (pos < indexTo) { + char ch = buffer.charAt(pos); + if (HTTP.isWhitespace(ch)) { + pos++; + } else { + break; + } + } + cursor.updatePos(pos); + if (cursor.atEnd()) { + return new NameValuePair[] {}; + } + + List params = new ArrayList(); + while (!cursor.atEnd()) { + NameValuePair param = parseNameValuePair(buffer, cursor); + params.add(param); + char ch = buffer.charAt(cursor.getPos() - 1); + if (ch == ELEM_DELIMITER) { + break; + } + } + + return (NameValuePair[]) + params.toArray(new NameValuePair[params.size()]); + } + + /** + * Parses a name-value-pair with the given parser. + * + * @param value the NVP to parse + * @param parser the parser to use, or <code>null</code> for default + * + * @return the parsed name-value pair + */ + public final static + NameValuePair parseNameValuePair(final String value, + HeaderValueParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null"); + } + + if (parser == null) + parser = BasicHeaderValueParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + ParserCursor cursor = new ParserCursor(0, value.length()); + return parser.parseNameValuePair(buffer, cursor); + } + + + // non-javadoc, see interface HeaderValueParser + public NameValuePair parseNameValuePair(final CharArrayBuffer buffer, + final ParserCursor cursor) { + return parseNameValuePair(buffer, cursor, ALL_DELIMITERS); + } + + private static boolean isOneOf(final char ch, final char[] chs) { + if (chs != null) { + for (int i = 0; i < chs.length; i++) { + if (ch == chs[i]) { + return true; + } + } + } + return false; + } + + public NameValuePair parseNameValuePair(final CharArrayBuffer buffer, + final ParserCursor cursor, + final char[] delimiters) { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + + boolean terminated = false; + + int pos = cursor.getPos(); + int indexFrom = cursor.getPos(); + int indexTo = cursor.getUpperBound(); + + // Find name + String name = null; + while (pos < indexTo) { + char ch = buffer.charAt(pos); + if (ch == '=') { + break; + } + if (isOneOf(ch, delimiters)) { + terminated = true; + break; + } + pos++; + } + + if (pos == indexTo) { + terminated = true; + name = buffer.substringTrimmed(indexFrom, indexTo); + } else { + name = buffer.substringTrimmed(indexFrom, pos); + pos++; + } + + if (terminated) { + cursor.updatePos(pos); + return createNameValuePair(name, null); + } + + // Find value + String value = null; + int i1 = pos; + + boolean qouted = false; + boolean escaped = false; + while (pos < indexTo) { + char ch = buffer.charAt(pos); + if (ch == '"' && !escaped) { + qouted = !qouted; + } + if (!qouted && !escaped && isOneOf(ch, delimiters)) { + terminated = true; + break; + } + if (escaped) { + escaped = false; + } else { + escaped = qouted && ch == '\\'; + } + pos++; + } + + int i2 = pos; + // Trim leading white spaces + while (i1 < i2 && (HTTP.isWhitespace(buffer.charAt(i1)))) { + i1++; + } + // Trim trailing white spaces + while ((i2 > i1) && (HTTP.isWhitespace(buffer.charAt(i2 - 1)))) { + i2--; + } + // Strip away quotes if necessary + if (((i2 - i1) >= 2) + && (buffer.charAt(i1) == '"') + && (buffer.charAt(i2 - 1) == '"')) { + i1++; + i2--; + } + value = buffer.substring(i1, i2); + if (terminated) { + pos++; + } + cursor.updatePos(pos); + return createNameValuePair(name, value); + } + + /** + * Creates a name-value pair. + * Called from {@link #parseNameValuePair}. + * + * @param name the name + * @param value the value, or <code>null</code> + * + * @return a name-value pair representing the arguments + */ + protected NameValuePair createNameValuePair(final String name, final String value) { + return new BasicNameValuePair(name, value); + } + +} + diff --git a/src/org/apache/http/message/BasicHttpEntityEnclosingRequest.java b/src/org/apache/http/message/BasicHttpEntityEnclosingRequest.java new file mode 100644 index 0000000..dbb70c8 --- /dev/null +++ b/src/org/apache/http/message/BasicHttpEntityEnclosingRequest.java @@ -0,0 +1,81 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHttpEntityEnclosingRequest.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.protocol.HTTP; + +/** + * Basic implementation of a request with an entity that can be modified. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public class BasicHttpEntityEnclosingRequest + extends BasicHttpRequest implements HttpEntityEnclosingRequest { + + private HttpEntity entity; + + public BasicHttpEntityEnclosingRequest(final String method, final String uri) { + super(method, uri); + } + + public BasicHttpEntityEnclosingRequest(final String method, final String uri, + final ProtocolVersion ver) { + this(new BasicRequestLine(method, uri, ver)); + } + + public BasicHttpEntityEnclosingRequest(final RequestLine requestline) { + super(requestline); + } + + public HttpEntity getEntity() { + return this.entity; + } + + public void setEntity(final HttpEntity entity) { + this.entity = entity; + } + + public boolean expectContinue() { + Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); + return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); + } + +} diff --git a/src/org/apache/http/message/BasicHttpRequest.java b/src/org/apache/http/message/BasicHttpRequest.java new file mode 100644 index 0000000..eedf8bc --- /dev/null +++ b/src/org/apache/http/message/BasicHttpRequest.java @@ -0,0 +1,98 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHttpRequest.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.HttpRequest; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.params.HttpProtocolParams; + +/** + * Basic implementation of an HTTP request that can be modified. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public class BasicHttpRequest extends AbstractHttpMessage implements HttpRequest { + + private final RequestLine requestline; + private final String method; + private final String uri; + + public BasicHttpRequest(final String method, final String uri) { + super(); + if (method == null) { + throw new IllegalArgumentException("Method name may not be null"); + } + if (uri == null) { + throw new IllegalArgumentException("Request URI may not be null"); + } + this.method = method; + this.uri = uri; + this.requestline = null; + } + + public BasicHttpRequest(final String method, final String uri, final ProtocolVersion ver) { + this(new BasicRequestLine(method, uri, ver)); + } + + public BasicHttpRequest(final RequestLine requestline) { + super(); + if (requestline == null) { + throw new IllegalArgumentException("Request line may not be null"); + } + this.requestline = requestline; + this.method = requestline.getMethod(); + this.uri = requestline.getUri(); + } + + public ProtocolVersion getProtocolVersion() { + if (this.requestline != null) { + return this.requestline.getProtocolVersion(); + } else { + return HttpProtocolParams.getVersion(getParams()); + } + } + + public RequestLine getRequestLine() { + if (this.requestline != null) { + return this.requestline; + } else { + ProtocolVersion ver = HttpProtocolParams.getVersion(getParams()); + return new BasicRequestLine(this.method, this.uri, ver); + } + } + +} diff --git a/src/org/apache/http/message/BasicHttpResponse.java b/src/org/apache/http/message/BasicHttpResponse.java new file mode 100644 index 0000000..7da4bea --- /dev/null +++ b/src/org/apache/http/message/BasicHttpResponse.java @@ -0,0 +1,204 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicHttpResponse.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import java.util.Locale; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.ReasonPhraseCatalog; + + +/** + * Basic implementation of an HTTP response that can be modified. + * This implementation makes sure that there always is a status line. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public class BasicHttpResponse extends AbstractHttpMessage + implements HttpResponse { + + private StatusLine statusline; + private HttpEntity entity; + private ReasonPhraseCatalog reasonCatalog; + private Locale locale; + + + /** + * Creates a new response. + * This is the constructor to which all others map. + * + * @param statusline the status line + * @param catalog the reason phrase catalog, or + * <code>null</code> to disable automatic + * reason phrase lookup + * @param locale the locale for looking up reason phrases, or + * <code>null</code> for the system locale + */ + public BasicHttpResponse(final StatusLine statusline, + final ReasonPhraseCatalog catalog, + final Locale locale) { + super(); + if (statusline == null) { + throw new IllegalArgumentException("Status line may not be null."); + } + this.statusline = statusline; + this.reasonCatalog = catalog; + this.locale = (locale != null) ? locale : Locale.getDefault(); + } + + /** + * Creates a response from a status line. + * The response will not have a reason phrase catalog and + * use the system default locale. + * + * @param statusline the status line + */ + public BasicHttpResponse(final StatusLine statusline) { + this(statusline, null, null); + } + + /** + * Creates a response from elements of a status line. + * The response will not have a reason phrase catalog and + * use the system default locale. + * + * @param ver the protocol version of the response + * @param code the status code of the response + * @param reason the reason phrase to the status code, or + * <code>null</code> + */ + public BasicHttpResponse(final ProtocolVersion ver, + final int code, + final String reason) { + this(new BasicStatusLine(ver, code, reason), null, null); + } + + + // non-javadoc, see interface HttpMessage + public ProtocolVersion getProtocolVersion() { + return this.statusline.getProtocolVersion(); + } + + // non-javadoc, see interface HttpResponse + public StatusLine getStatusLine() { + return this.statusline; + } + + // non-javadoc, see interface HttpResponse + public HttpEntity getEntity() { + return this.entity; + } + + // non-javadoc, see interface HttpResponse + public Locale getLocale() { + return this.locale; + } + + // non-javadoc, see interface HttpResponse + public void setStatusLine(final StatusLine statusline) { + if (statusline == null) { + throw new IllegalArgumentException("Status line may not be null"); + } + this.statusline = statusline; + } + + // non-javadoc, see interface HttpResponse + public void setStatusLine(final ProtocolVersion ver, final int code) { + // arguments checked in BasicStatusLine constructor + this.statusline = new BasicStatusLine(ver, code, getReason(code)); + } + + // non-javadoc, see interface HttpResponse + public void setStatusLine(final ProtocolVersion ver, final int code, + final String reason) { + // arguments checked in BasicStatusLine constructor + this.statusline = new BasicStatusLine(ver, code, reason); + } + + // non-javadoc, see interface HttpResponse + public void setStatusCode(int code) { + // argument checked in BasicStatusLine constructor + ProtocolVersion ver = this.statusline.getProtocolVersion(); + this.statusline = new BasicStatusLine(ver, code, getReason(code)); + } + + // non-javadoc, see interface HttpResponse + public void setReasonPhrase(String reason) { + + if ((reason != null) && ((reason.indexOf('\n') >= 0) || + (reason.indexOf('\r') >= 0)) + ) { + throw new IllegalArgumentException("Line break in reason phrase."); + } + this.statusline = new BasicStatusLine(this.statusline.getProtocolVersion(), + this.statusline.getStatusCode(), + reason); + } + + // non-javadoc, see interface HttpResponse + public void setEntity(final HttpEntity entity) { + this.entity = entity; + } + + // non-javadoc, see interface HttpResponse + public void setLocale(Locale loc) { + if (loc == null) { + throw new IllegalArgumentException("Locale may not be null."); + } + this.locale = loc; + final int code = this.statusline.getStatusCode(); + this.statusline = new BasicStatusLine + (this.statusline.getProtocolVersion(), code, getReason(code)); + } + + /** + * Looks up a reason phrase. + * This method evaluates the currently set catalog and locale. + * It also handles a missing catalog. + * + * @param code the status code for which to look up the reason + * + * @return the reason phrase, or <code>null</code> if there is none + */ + protected String getReason(int code) { + return (this.reasonCatalog == null) ? + null : this.reasonCatalog.getReason(code, this.locale); + } + +} diff --git a/src/org/apache/http/message/BasicLineFormatter.java b/src/org/apache/http/message/BasicLineFormatter.java new file mode 100644 index 0000000..7c3bbc4 --- /dev/null +++ b/src/org/apache/http/message/BasicLineFormatter.java @@ -0,0 +1,346 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicLineFormatter.java $ + * $Revision: 574185 $ + * $Date: 2007-09-10 02:19:47 -0700 (Mon, 10 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.StatusLine; +import org.apache.http.Header; +import org.apache.http.FormattedHeader; +import org.apache.http.util.CharArrayBuffer; + + +/** + * Interface for formatting elements of the HEAD section of an HTTP message. + * This is the complement to {@link LineParser}. + * There are individual methods for formatting a request line, a + * status line, or a header line. The formatting does <i>not</i> include the + * trailing line break sequence CR-LF. + * The formatted lines are returned in memory, the formatter does not depend + * on any specific IO mechanism. + * Instances of this interface are expected to be stateless and thread-safe. + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author and others + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 574185 $ + * + * @since 4.0 + */ +public class BasicLineFormatter implements LineFormatter { + + /** + * A default instance of this class, for use as default or fallback. + * Note that {@link BasicLineFormatter} is not a singleton, there can + * be many instances of the class itself and of derived classes. + * The instance here provides non-customized, default behavior. + */ + public final static BasicLineFormatter DEFAULT = new BasicLineFormatter(); + + + + // public default constructor + + + /** + * Obtains a buffer for formatting. + * + * @param buffer a buffer already available, or <code>null</code> + * + * @return the cleared argument buffer if there is one, or + * a new empty buffer that can be used for formatting + */ + protected CharArrayBuffer initBuffer(CharArrayBuffer buffer) { + if (buffer != null) { + buffer.clear(); + } else { + buffer = new CharArrayBuffer(64); + } + return buffer; + } + + + /** + * Formats a protocol version. + * + * @param version the protocol version to format + * @param formatter the formatter to use, or + * <code>null</code> for the + * {@link #DEFAULT default} + * + * @return the formatted protocol version + */ + public final static + String formatProtocolVersion(final ProtocolVersion version, + LineFormatter formatter) { + if (formatter == null) + formatter = BasicLineFormatter.DEFAULT; + return formatter.appendProtocolVersion(null, version).toString(); + } + + + // non-javadoc, see interface LineFormatter + public CharArrayBuffer appendProtocolVersion(final CharArrayBuffer buffer, + final ProtocolVersion version) { + if (version == null) { + throw new IllegalArgumentException + ("Protocol version may not be null"); + } + + // can't use initBuffer, that would clear the argument! + CharArrayBuffer result = buffer; + final int len = estimateProtocolVersionLen(version); + if (result == null) { + result = new CharArrayBuffer(len); + } else { + result.ensureCapacity(len); + } + + result.append(version.getProtocol()); + result.append('/'); + result.append(Integer.toString(version.getMajor())); + result.append('.'); + result.append(Integer.toString(version.getMinor())); + + return result; + } + + + /** + * Guesses the length of a formatted protocol version. + * Needed to guess the length of a formatted request or status line. + * + * @param version the protocol version to format, or <code>null</code> + * + * @return the estimated length of the formatted protocol version, + * in characters + */ + protected int estimateProtocolVersionLen(final ProtocolVersion version) { + return version.getProtocol().length() + 4; // room for "HTTP/1.1" + } + + + /** + * Formats a request line. + * + * @param reqline the request line to format + * @param formatter the formatter to use, or + * <code>null</code> for the + * {@link #DEFAULT default} + * + * @return the formatted request line + */ + public final static String formatRequestLine(final RequestLine reqline, + LineFormatter formatter) { + if (formatter == null) + formatter = BasicLineFormatter.DEFAULT; + return formatter.formatRequestLine(null, reqline).toString(); + } + + + // non-javadoc, see interface LineFormatter + public CharArrayBuffer formatRequestLine(CharArrayBuffer buffer, + RequestLine reqline) { + if (reqline == null) { + throw new IllegalArgumentException + ("Request line may not be null"); + } + + CharArrayBuffer result = initBuffer(buffer); + doFormatRequestLine(result, reqline); + + return result; + } + + + /** + * Actually formats a request line. + * Called from {@link #formatRequestLine}. + * + * @param buffer the empty buffer into which to format, + * never <code>null</code> + * @param reqline the request line to format, never <code>null</code> + */ + protected void doFormatRequestLine(final CharArrayBuffer buffer, + final RequestLine reqline) { + final String method = reqline.getMethod(); + final String uri = reqline.getUri(); + + // room for "GET /index.html HTTP/1.1" + int len = method.length() + 1 + uri.length() + 1 + + estimateProtocolVersionLen(reqline.getProtocolVersion()); + buffer.ensureCapacity(len); + + buffer.append(method); + buffer.append(' '); + buffer.append(uri); + buffer.append(' '); + appendProtocolVersion(buffer, reqline.getProtocolVersion()); + } + + + + /** + * Formats a status line. + * + * @param statline the status line to format + * @param formatter the formatter to use, or + * <code>null</code> for the + * {@link #DEFAULT default} + * + * @return the formatted status line + */ + public final static String formatStatusLine(final StatusLine statline, + LineFormatter formatter) { + if (formatter == null) + formatter = BasicLineFormatter.DEFAULT; + return formatter.formatStatusLine(null, statline).toString(); + } + + + // non-javadoc, see interface LineFormatter + public CharArrayBuffer formatStatusLine(final CharArrayBuffer buffer, + final StatusLine statline) { + if (statline == null) { + throw new IllegalArgumentException + ("Status line may not be null"); + } + + CharArrayBuffer result = initBuffer(buffer); + doFormatStatusLine(result, statline); + + return result; + } + + + /** + * Actually formats a status line. + * Called from {@link #formatStatusLine}. + * + * @param buffer the empty buffer into which to format, + * never <code>null</code> + * @param statline the status line to format, never <code>null</code> + */ + protected void doFormatStatusLine(final CharArrayBuffer buffer, + final StatusLine statline) { + + int len = estimateProtocolVersionLen(statline.getProtocolVersion()) + + 1 + 3 + 1; // room for "HTTP/1.1 200 " + final String reason = statline.getReasonPhrase(); + if (reason != null) { + len += reason.length(); + } + buffer.ensureCapacity(len); + + appendProtocolVersion(buffer, statline.getProtocolVersion()); + buffer.append(' '); + buffer.append(Integer.toString(statline.getStatusCode())); + buffer.append(' '); // keep whitespace even if reason phrase is empty + if (reason != null) { + buffer.append(reason); + } + } + + + /** + * Formats a header. + * + * @param header the header to format + * @param formatter the formatter to use, or + * <code>null</code> for the + * {@link #DEFAULT default} + * + * @return the formatted header + */ + public final static String formatHeader(final Header header, + LineFormatter formatter) { + if (formatter == null) + formatter = BasicLineFormatter.DEFAULT; + return formatter.formatHeader(null, header).toString(); + } + + + // non-javadoc, see interface LineFormatter + public CharArrayBuffer formatHeader(CharArrayBuffer buffer, + Header header) { + if (header == null) { + throw new IllegalArgumentException + ("Header may not be null"); + } + CharArrayBuffer result = null; + + if (header instanceof FormattedHeader) { + // If the header is backed by a buffer, re-use the buffer + result = ((FormattedHeader)header).getBuffer(); + } else { + result = initBuffer(buffer); + doFormatHeader(result, header); + } + return result; + + } // formatHeader + + + /** + * Actually formats a header. + * Called from {@link #formatHeader}. + * + * @param buffer the empty buffer into which to format, + * never <code>null</code> + * @param header the header to format, never <code>null</code> + */ + protected void doFormatHeader(final CharArrayBuffer buffer, + final Header header) { + final String name = header.getName(); + final String value = header.getValue(); + + int len = name.length() + 2; + if (value != null) { + len += value.length(); + } + buffer.ensureCapacity(len); + + buffer.append(name); + buffer.append(": "); + if (value != null) { + buffer.append(value); + } + } + + +} // class BasicLineFormatter diff --git a/src/org/apache/http/message/BasicLineParser.java b/src/org/apache/http/message/BasicLineParser.java new file mode 100644 index 0000000..c5e9ddb --- /dev/null +++ b/src/org/apache/http/message/BasicLineParser.java @@ -0,0 +1,504 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicLineParser.java $ + * $Revision: 591798 $ + * $Date: 2007-11-04 08:19:29 -0800 (Sun, 04 Nov 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.ParseException; +import org.apache.http.RequestLine; +import org.apache.http.StatusLine; +import org.apache.http.Header; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.CharArrayBuffer; + + +/** + * Basic parser for lines in the head section of an HTTP message. + * There are individual methods for parsing a request line, a + * status line, or a header line. + * The lines to parse are passed in memory, the parser does not depend + * on any specific IO mechanism. + * Instances of this class are stateless and thread-safe. + * Derived classes MUST maintain these properties. + * + * <p> + * Note: This class was created by refactoring parsing code located in + * various other classes. The author tags from those other classes have + * been replicated here, although the association with the parsing code + * taken from there has not been traced. + * </p> + * + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * @author and others + */ +public class BasicLineParser implements LineParser { + + /** + * A default instance of this class, for use as default or fallback. + * Note that {@link BasicLineParser} is not a singleton, there can + * be many instances of the class itself and of derived classes. + * The instance here provides non-customized, default behavior. + */ + public final static BasicLineParser DEFAULT = new BasicLineParser(); + + + /** + * A version of the protocol to parse. + * The version is typically not relevant, but the protocol name. + */ + protected final ProtocolVersion protocol; + + + /** + * Creates a new line parser for the given HTTP-like protocol. + * + * @param proto a version of the protocol to parse, or + * <code>null</code> for HTTP. The actual version + * is not relevant, only the protocol name. + */ + public BasicLineParser(ProtocolVersion proto) { + if (proto == null) { + proto = HttpVersion.HTTP_1_1; + } + this.protocol = proto; + } + + + /** + * Creates a new line parser for HTTP. + */ + public BasicLineParser() { + this(null); + } + + + + public final static + ProtocolVersion parseProtocolVersion(String value, + LineParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null."); + } + + if (parser == null) + parser = BasicLineParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + ParserCursor cursor = new ParserCursor(0, value.length()); + return parser.parseProtocolVersion(buffer, cursor); + } + + + // non-javadoc, see interface LineParser + public ProtocolVersion parseProtocolVersion(final CharArrayBuffer buffer, + final ParserCursor cursor) + throws ParseException { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + + final String protoname = this.protocol.getProtocol(); + final int protolength = protoname.length(); + + int indexFrom = cursor.getPos(); + int indexTo = cursor.getUpperBound(); + + skipWhitespace(buffer, cursor); + + int i = cursor.getPos(); + + // long enough for "HTTP/1.1"? + if (i + protolength + 4 > indexTo) { + throw new ParseException + ("Not a valid protocol version: " + + buffer.substring(indexFrom, indexTo)); + } + + // check the protocol name and slash + boolean ok = true; + for (int j=0; ok && (j<protolength); j++) { + ok = (buffer.charAt(i+j) == protoname.charAt(j)); + } + if (ok) { + ok = (buffer.charAt(i+protolength) == '/'); + } + if (!ok) { + throw new ParseException + ("Not a valid protocol version: " + + buffer.substring(indexFrom, indexTo)); + } + + i += protolength+1; + + int period = buffer.indexOf('.', i, indexTo); + if (period == -1) { + throw new ParseException + ("Invalid protocol version number: " + + buffer.substring(indexFrom, indexTo)); + } + int major; + try { + major = Integer.parseInt(buffer.substringTrimmed(i, period)); + } catch (NumberFormatException e) { + throw new ParseException + ("Invalid protocol major version number: " + + buffer.substring(indexFrom, indexTo)); + } + i = period + 1; + + int blank = buffer.indexOf(' ', i, indexTo); + if (blank == -1) { + blank = indexTo; + } + int minor; + try { + minor = Integer.parseInt(buffer.substringTrimmed(i, blank)); + } catch (NumberFormatException e) { + throw new ParseException( + "Invalid protocol minor version number: " + + buffer.substring(indexFrom, indexTo)); + } + + cursor.updatePos(blank); + + return createProtocolVersion(major, minor); + + } // parseProtocolVersion + + + /** + * Creates a protocol version. + * Called from {@link #parseProtocolVersion}. + * + * @param major the major version number, for example 1 in HTTP/1.0 + * @param minor the minor version number, for example 0 in HTTP/1.0 + * + * @return the protocol version + */ + protected ProtocolVersion createProtocolVersion(int major, int minor) { + return protocol.forVersion(major, minor); + } + + + + // non-javadoc, see interface LineParser + public boolean hasProtocolVersion(final CharArrayBuffer buffer, + final ParserCursor cursor) { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + int index = cursor.getPos(); + + final String protoname = this.protocol.getProtocol(); + final int protolength = protoname.length(); + + if (buffer.length() < protolength+4) + return false; // not long enough for "HTTP/1.1" + + if (index < 0) { + // end of line, no tolerance for trailing whitespace + // this works only for single-digit major and minor version + index = buffer.length() -4 -protolength; + } else if (index == 0) { + // beginning of line, tolerate leading whitespace + while ((index < buffer.length()) && + HTTP.isWhitespace(buffer.charAt(index))) { + index++; + } + } // else within line, don't tolerate whitespace + + + if (index + protolength + 4 > buffer.length()) + return false; + + + // just check protocol name and slash, no need to analyse the version + boolean ok = true; + for (int j=0; ok && (j<protolength); j++) { + ok = (buffer.charAt(index+j) == protoname.charAt(j)); + } + if (ok) { + ok = (buffer.charAt(index+protolength) == '/'); + } + + return ok; + } + + + + public final static + RequestLine parseRequestLine(final String value, + LineParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null."); + } + + if (parser == null) + parser = BasicLineParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + ParserCursor cursor = new ParserCursor(0, value.length()); + return parser.parseRequestLine(buffer, cursor); + } + + + /** + * Parses a request line. + * + * @param buffer a buffer holding the line to parse + * + * @return the parsed request line + * + * @throws ParseException in case of a parse error + */ + public RequestLine parseRequestLine(final CharArrayBuffer buffer, + final ParserCursor cursor) + throws ParseException { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + + int indexFrom = cursor.getPos(); + int indexTo = cursor.getUpperBound(); + + try { + skipWhitespace(buffer, cursor); + int i = cursor.getPos(); + + int blank = buffer.indexOf(' ', i, indexTo); + if (blank < 0) { + throw new ParseException("Invalid request line: " + + buffer.substring(indexFrom, indexTo)); + } + String method = buffer.substringTrimmed(i, blank); + cursor.updatePos(blank); + + skipWhitespace(buffer, cursor); + i = cursor.getPos(); + + blank = buffer.indexOf(' ', i, indexTo); + if (blank < 0) { + throw new ParseException("Invalid request line: " + + buffer.substring(indexFrom, indexTo)); + } + String uri = buffer.substringTrimmed(i, blank); + cursor.updatePos(blank); + + ProtocolVersion ver = parseProtocolVersion(buffer, cursor); + + skipWhitespace(buffer, cursor); + if (!cursor.atEnd()) { + throw new ParseException("Invalid request line: " + + buffer.substring(indexFrom, indexTo)); + } + + return createRequestLine(method, uri, ver); + } catch (IndexOutOfBoundsException e) { + throw new ParseException("Invalid request line: " + + buffer.substring(indexFrom, indexTo)); + } + } // parseRequestLine + + + /** + * Instantiates a new request line. + * Called from {@link #parseRequestLine}. + * + * @param method the request method + * @param uri the requested URI + * @param ver the protocol version + * + * @return a new status line with the given data + */ + protected RequestLine createRequestLine(final String method, + final String uri, + final ProtocolVersion ver) { + return new BasicRequestLine(method, uri, ver); + } + + + + public final static + StatusLine parseStatusLine(final String value, + LineParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null."); + } + + if (parser == null) + parser = BasicLineParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + ParserCursor cursor = new ParserCursor(0, value.length()); + return parser.parseStatusLine(buffer, cursor); + } + + + // non-javadoc, see interface LineParser + public StatusLine parseStatusLine(final CharArrayBuffer buffer, + final ParserCursor cursor) + throws ParseException { + + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + + int indexFrom = cursor.getPos(); + int indexTo = cursor.getUpperBound(); + + try { + // handle the HTTP-Version + ProtocolVersion ver = parseProtocolVersion(buffer, cursor); + + // handle the Status-Code + skipWhitespace(buffer, cursor); + int i = cursor.getPos(); + + int blank = buffer.indexOf(' ', i, indexTo); + if (blank < 0) { + blank = indexTo; + } + int statusCode = 0; + try { + statusCode = + Integer.parseInt(buffer.substringTrimmed(i, blank)); + } catch (NumberFormatException e) { + throw new ParseException( + "Unable to parse status code from status line: " + + buffer.substring(indexFrom, indexTo)); + } + //handle the Reason-Phrase + i = blank; + String reasonPhrase = null; + if (i < indexTo) { + reasonPhrase = buffer.substringTrimmed(i, indexTo); + } else { + reasonPhrase = ""; + } + return createStatusLine(ver, statusCode, reasonPhrase); + + } catch (IndexOutOfBoundsException e) { + throw new ParseException("Invalid status line: " + + buffer.substring(indexFrom, indexTo)); + } + } // parseStatusLine + + + /** + * Instantiates a new status line. + * Called from {@link #parseStatusLine}. + * + * @param ver the protocol version + * @param status the status code + * @param reason the reason phrase + * + * @return a new status line with the given data + */ + protected StatusLine createStatusLine(final ProtocolVersion ver, + final int status, + final String reason) { + return new BasicStatusLine(ver, status, reason); + } + + + + public final static + Header parseHeader(final String value, + LineParser parser) + throws ParseException { + + if (value == null) { + throw new IllegalArgumentException + ("Value to parse may not be null"); + } + + if (parser == null) + parser = BasicLineParser.DEFAULT; + + CharArrayBuffer buffer = new CharArrayBuffer(value.length()); + buffer.append(value); + return parser.parseHeader(buffer); + } + + + // non-javadoc, see interface LineParser + public Header parseHeader(CharArrayBuffer buffer) + throws ParseException { + + // the actual parser code is in the constructor of BufferedHeader + return new BufferedHeader(buffer); + } + + + /** + * Helper to skip whitespace. + */ + protected void skipWhitespace(final CharArrayBuffer buffer, final ParserCursor cursor) { + int pos = cursor.getPos(); + int indexTo = cursor.getUpperBound(); + while ((pos < indexTo) && + HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + cursor.updatePos(pos); + } + +} // class BasicLineParser diff --git a/src/org/apache/http/message/BasicListHeaderIterator.java b/src/org/apache/http/message/BasicListHeaderIterator.java new file mode 100644 index 0000000..69b8c06 --- /dev/null +++ b/src/org/apache/http/message/BasicListHeaderIterator.java @@ -0,0 +1,196 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicListHeaderIterator.java $ + * $Revision: 584542 $ + * $Date: 2007-10-14 06:29:34 -0700 (Sun, 14 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.http.Header; +import org.apache.http.HeaderIterator; + + +/** + * Implementation of a {@link HeaderIterator} based on a {@link List}. + * For use by {@link HeaderGroup}. + * + * @version $Revision: 584542 $ + */ +public class BasicListHeaderIterator implements HeaderIterator { + + /** + * A list of headers to iterate over. + * Not all elements of this array are necessarily part of the iteration. + */ + protected final List allHeaders; + + + /** + * The position of the next header in {@link #allHeaders allHeaders}. + * Negative if the iteration is over. + */ + protected int currentIndex; + + + /** + * The position of the last returned header. + * Negative if none has been returned so far. + */ + protected int lastIndex; + + + /** + * The header name to filter by. + * <code>null</code> to iterate over all headers in the array. + */ + protected String headerName; + + + + /** + * Creates a new header iterator. + * + * @param headers a list of headers over which to iterate + * @param name the name of the headers over which to iterate, or + * <code>null</code> for any + */ + public BasicListHeaderIterator(List headers, String name) { + if (headers == null) { + throw new IllegalArgumentException + ("Header list must not be null."); + } + + this.allHeaders = headers; + this.headerName = name; + this.currentIndex = findNext(-1); + this.lastIndex = -1; + } + + + /** + * Determines the index of the next header. + * + * @param from one less than the index to consider first, + * -1 to search for the first header + * + * @return the index of the next header that matches the filter name, + * or negative if there are no more headers + */ + protected int findNext(int from) { + if (from < -1) + return -1; + + final int to = this.allHeaders.size()-1; + boolean found = false; + while (!found && (from < to)) { + from++; + found = filterHeader(from); + } + return found ? from : -1; + } + + + /** + * Checks whether a header is part of the iteration. + * + * @param index the index of the header to check + * + * @return <code>true</code> if the header should be part of the + * iteration, <code>false</code> to skip + */ + protected boolean filterHeader(int index) { + if (this.headerName == null) + return true; + + // non-header elements, including null, will trigger exceptions + final String name = ((Header)this.allHeaders.get(index)).getName(); + + return this.headerName.equalsIgnoreCase(name); + } + + + // non-javadoc, see interface HeaderIterator + public boolean hasNext() { + return (this.currentIndex >= 0); + } + + + /** + * Obtains the next header from this iteration. + * + * @return the next header in this iteration + * + * @throws NoSuchElementException if there are no more headers + */ + public Header nextHeader() + throws NoSuchElementException { + + final int current = this.currentIndex; + if (current < 0) { + throw new NoSuchElementException("Iteration already finished."); + } + + this.lastIndex = current; + this.currentIndex = findNext(current); + + return (Header) this.allHeaders.get(current); + } + + + /** + * Returns the next header. + * Same as {@link #nextHeader nextHeader}, but not type-safe. + * + * @return the next header in this iteration + * + * @throws NoSuchElementException if there are no more headers + */ + public final Object next() + throws NoSuchElementException { + return nextHeader(); + } + + + /** + * Removes the header that was returned last. + */ + public void remove() + throws UnsupportedOperationException { + + if (this.lastIndex < 0) { + throw new IllegalStateException("No header to remove."); + } + this.allHeaders.remove(this.lastIndex); + this.lastIndex = -1; + this.currentIndex--; // adjust for the removed element + } +} diff --git a/src/org/apache/http/message/BasicNameValuePair.java b/src/org/apache/http/message/BasicNameValuePair.java new file mode 100644 index 0000000..59fcb42 --- /dev/null +++ b/src/org/apache/http/message/BasicNameValuePair.java @@ -0,0 +1,189 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicNameValuePair.java $ + * $Revision: 604625 $ + * $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.NameValuePair; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.LangUtils; + +/** + * A simple class encapsulating an attribute/value pair. + * <p> + * This class comforms to the generic grammar and formatting rules outlined in the + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2">Section 2.2</a> + * and + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a> + * of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a> + * </p> + * <h>2.2 Basic Rules</h> + * <p> + * The following rules are used throughout this specification to describe basic parsing constructs. + * The US-ASCII coded character set is defined by ANSI X3.4-1986. + * </p> + * <pre> + * OCTET = <any 8-bit sequence of data> + * CHAR = <any US-ASCII character (octets 0 - 127)> + * UPALPHA = <any US-ASCII uppercase letter "A".."Z"> + * LOALPHA = <any US-ASCII lowercase letter "a".."z"> + * ALPHA = UPALPHA | LOALPHA + * DIGIT = <any US-ASCII digit "0".."9"> + * CTL = <any US-ASCII control character + * (octets 0 - 31) and DEL (127)> + * CR = <US-ASCII CR, carriage return (13)> + * LF = <US-ASCII LF, linefeed (10)> + * SP = <US-ASCII SP, space (32)> + * HT = <US-ASCII HT, horizontal-tab (9)> + * <"> = <US-ASCII double-quote mark (34)> + * </pre> + * <p> + * Many HTTP/1.1 header field values consist of words separated by LWS or special + * characters. These special characters MUST be in a quoted string to be used within + * a parameter value (as defined in section 3.6). + * <p> + * <pre> + * token = 1*<any CHAR except CTLs or separators> + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + * </pre> + * <p> + * A string of text is parsed as a single word if it is quoted using double-quote marks. + * </p> + * <pre> + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = <any TEXT except <">> + * </pre> + * <p> + * The backslash character ("\") MAY be used as a single-character quoting mechanism only + * within quoted-string and comment constructs. + * </p> + * <pre> + * quoted-pair = "\" CHAR + * </pre> + * <h>3.6 Transfer Codings</h> + * <p> + * Parameters are in the form of attribute/value pairs. + * </p> + * <pre> + * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + * </pre> + * + * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a> + * + */ +public class BasicNameValuePair implements NameValuePair, Cloneable { + + private final String name; + private final String value; + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicNameValuePair(final String name, final String value) { + super(); + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + this.name = name; + this.value = value; + } + + /** + * Returns the name. + * + * @return String name The name + */ + public String getName() { + return this.name; + } + + /** + * Returns the value. + * + * @return String value The current value. + */ + public String getValue() { + return this.value; + } + + + /** + * Get a string representation of this pair. + * + * @return A string representation. + */ + public String toString() { + // don't call complex default formatting for a simple toString + + int len = this.name.length(); + if (this.value != null) + len += 1 + this.value.length(); + CharArrayBuffer buffer = new CharArrayBuffer(len); + + buffer.append(this.name); + if (this.value != null) { + buffer.append("="); + buffer.append(this.value); + } + return buffer.toString(); + } + + public boolean equals(final Object object) { + if (object == null) return false; + if (this == object) return true; + if (object instanceof NameValuePair) { + BasicNameValuePair that = (BasicNameValuePair) object; + return this.name.equals(that.name) + && LangUtils.equals(this.value, that.value); + } else { + return false; + } + } + + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, this.name); + hash = LangUtils.hashCode(hash, this.value); + return hash; + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/org/apache/http/message/BasicRequestLine.java b/src/org/apache/http/message/BasicRequestLine.java new file mode 100644 index 0000000..b826064 --- /dev/null +++ b/src/org/apache/http/message/BasicRequestLine.java @@ -0,0 +1,99 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicRequestLine.java $ + * $Revision: 604625 $ + * $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; + +/** + * The first line of an {@link org.apache.http.HttpRequest HttpRequest}. + * It contains the method, URI, and HTTP version of the request. + * For details, see RFC 2616. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 604625 $ + * + * @since 4.0 + */ +public class BasicRequestLine implements RequestLine, Cloneable { + + private final ProtocolVersion protoversion; + private final String method; + private final String uri; + + public BasicRequestLine(final String method, + final String uri, + final ProtocolVersion version) { + super(); + if (method == null) { + throw new IllegalArgumentException + ("Method must not be null."); + } + if (uri == null) { + throw new IllegalArgumentException + ("URI must not be null."); + } + if (version == null) { + throw new IllegalArgumentException + ("Protocol version must not be null."); + } + this.method = method; + this.uri = uri; + this.protoversion = version; + } + + public String getMethod() { + return this.method; + } + + public ProtocolVersion getProtocolVersion() { + return this.protoversion; + } + + public String getUri() { + return this.uri; + } + + public String toString() { + // no need for non-default formatting in toString() + return BasicLineFormatter.DEFAULT + .formatRequestLine(null, this).toString(); + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/org/apache/http/message/BasicStatusLine.java b/src/org/apache/http/message/BasicStatusLine.java new file mode 100644 index 0000000..c34cefe --- /dev/null +++ b/src/org/apache/http/message/BasicStatusLine.java @@ -0,0 +1,124 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicStatusLine.java $ + * $Revision: 604625 $ + * $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.HttpStatus; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; + + + +/** + * Represents a status line as returned from a HTTP server. + * See <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a> section 6.1. + * This class is immutable and therefore inherently thread safe. + * + * @see HttpStatus + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @version $Id: BasicStatusLine.java 604625 2007-12-16 14:11:11Z olegk $ + * + * @since 4.0 + */ +public class BasicStatusLine implements StatusLine, Cloneable { + + // ----------------------------------------------------- Instance Variables + + /** The protocol version. */ + private final ProtocolVersion protoVersion; + + /** The status code. */ + private final int statusCode; + + /** The reason phrase. */ + private final String reasonPhrase; + + // ----------------------------------------------------------- Constructors + /** + * Creates a new status line with the given version, status, and reason. + * + * @param version the protocol version of the response + * @param statusCode the status code of the response + * @param reasonPhrase the reason phrase to the status code, or + * <code>null</code> + */ + public BasicStatusLine(final ProtocolVersion version, int statusCode, + final String reasonPhrase) { + super(); + if (version == null) { + throw new IllegalArgumentException + ("Protocol version may not be null."); + } + if (statusCode < 0) { + throw new IllegalArgumentException + ("Status code may not be negative."); + } + this.protoVersion = version; + this.statusCode = statusCode; + this.reasonPhrase = reasonPhrase; + } + + // --------------------------------------------------------- Public Methods + + /** + * @return the Status-Code + */ + public int getStatusCode() { + return this.statusCode; + } + + /** + * @return the HTTP-Version + */ + public ProtocolVersion getProtocolVersion() { + return this.protoVersion; + } + + /** + * @return the Reason-Phrase + */ + public String getReasonPhrase() { + return this.reasonPhrase; + } + + public String toString() { + // no need for non-default formatting in toString() + return BasicLineFormatter.DEFAULT + .formatStatusLine(null, this).toString(); + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/src/org/apache/http/message/BasicTokenIterator.java b/src/org/apache/http/message/BasicTokenIterator.java new file mode 100644 index 0000000..5fbf5ba --- /dev/null +++ b/src/org/apache/http/message/BasicTokenIterator.java @@ -0,0 +1,429 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BasicTokenIterator.java $ + * $Revision: 602520 $ + * $Date: 2007-12-08 09:42:26 -0800 (Sat, 08 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import java.util.NoSuchElementException; + +import org.apache.http.HeaderIterator; +import org.apache.http.ParseException; +import org.apache.http.TokenIterator; + +/** + * Basic implementation of a {@link TokenIterator}. + * This implementation parses <tt>#token<tt> sequences as + * defined by RFC 2616, section 2. + * It extends that definition somewhat beyond US-ASCII. + * + * @version $Revision: 602520 $ + */ +public class BasicTokenIterator implements TokenIterator { + + /** The HTTP separator characters. Defined in RFC 2616, section 2.2. */ + // the order of the characters here is adjusted to put the + // most likely candidates at the beginning of the collection + public final static String HTTP_SEPARATORS = " ,;=()<>@:\\\"/[]?{}\t"; + + + /** The iterator from which to obtain the next header. */ + protected final HeaderIterator headerIt; + + /** + * The value of the current header. + * This is the header value that includes {@link #currentToken}. + * Undefined if the iteration is over. + */ + protected String currentHeader; + + /** + * The token to be returned by the next call to {@link #currentToken}. + * <code>null</code> if the iteration is over. + */ + protected String currentToken; + + /** + * The position after {@link #currentToken} in {@link #currentHeader}. + * Undefined if the iteration is over. + */ + protected int searchPos; + + + /** + * Creates a new instance of {@link BasicTokenIterator}. + * + * @param headerIterator the iterator for the headers to tokenize + */ + public BasicTokenIterator(final HeaderIterator headerIterator) { + if (headerIterator == null) { + throw new IllegalArgumentException + ("Header iterator must not be null."); + } + + this.headerIt = headerIterator; + this.searchPos = findNext(-1); + } + + + // non-javadoc, see interface TokenIterator + public boolean hasNext() { + return (this.currentToken != null); + } + + + /** + * Obtains the next token from this iteration. + * + * @return the next token in this iteration + * + * @throws NoSuchElementException if the iteration is already over + * @throws ParseException if an invalid header value is encountered + */ + public String nextToken() + throws NoSuchElementException, ParseException { + + if (this.currentToken == null) { + throw new NoSuchElementException("Iteration already finished."); + } + + final String result = this.currentToken; + // updates currentToken, may trigger ParseException: + this.searchPos = findNext(this.searchPos); + + return result; + } + + + /** + * Returns the next token. + * Same as {@link #nextToken}, but with generic return type. + * + * @return the next token in this iteration + * + * @throws NoSuchElementException if there are no more tokens + * @throws ParseException if an invalid header value is encountered + */ + public final Object next() + throws NoSuchElementException, ParseException { + return nextToken(); + } + + + /** + * Removing tokens is not supported. + * + * @throws UnsupportedOperationException always + */ + public final void remove() + throws UnsupportedOperationException { + + throw new UnsupportedOperationException + ("Removing tokens is not supported."); + } + + + /** + * Determines the next token. + * If found, the token is stored in {@link #currentToken}. + * The return value indicates the position after the token + * in {@link #currentHeader}. If necessary, the next header + * will be obtained from {@link #headerIt}. + * If not found, {@link #currentToken} is set to <code>null</code>. + * + * @param from the position in the current header at which to + * start the search, -1 to search in the first header + * + * @return the position after the found token in the current header, or + * negative if there was no next token + * + * @throws ParseException if an invalid header value is encountered + */ + protected int findNext(int from) + throws ParseException { + + if (from < 0) { + // called from the constructor, initialize the first header + if (!this.headerIt.hasNext()) { + return -1; + } + this.currentHeader = this.headerIt.nextHeader().getValue(); + from = 0; + } else { + // called after a token, make sure there is a separator + from = findTokenSeparator(from); + } + + int start = findTokenStart(from); + if (start < 0) { + this.currentToken = null; + return -1; // nothing found + } + + int end = findTokenEnd(start); + this.currentToken = createToken(this.currentHeader, start, end); + return end; + } + + + /** + * Creates a new token to be returned. + * Called from {@link #findNext findNext} after the token is identified. + * The default implementation simply calls + * {@link java.lang.String#substring String.substring}. + * <br/> + * If header values are significantly longer than tokens, and some + * tokens are permanently referenced by the application, there can + * be problems with garbage collection. A substring will hold a + * reference to the full characters of the original string and + * therefore occupies more memory than might be expected. + * To avoid this, override this method and create a new string + * instead of a substring. + * + * @param value the full header value from which to create a token + * @param start the index of the first token character + * @param end the index after the last token character + * + * @return a string representing the token identified by the arguments + */ + protected String createToken(String value, int start, int end) { + return value.substring(start, end); + } + + + /** + * Determines the starting position of the next token. + * This method will iterate over headers if necessary. + * + * @param from the position in the current header at which to + * start the search + * + * @return the position of the token start in the current header, + * negative if no token start could be found + */ + protected int findTokenStart(int from) { + if (from < 0) { + throw new IllegalArgumentException + ("Search position must not be negative: " + from); + } + + boolean found = false; + while (!found && (this.currentHeader != null)) { + + final int to = this.currentHeader.length(); + while (!found && (from < to)) { + + final char ch = this.currentHeader.charAt(from); + if (isTokenSeparator(ch) || isWhitespace(ch)) { + // whitspace and token separators are skipped + from++; + } else if (isTokenChar(this.currentHeader.charAt(from))) { + // found the start of a token + found = true; + } else { + throw new ParseException + ("Invalid character before token (pos " + from + + "): " + this.currentHeader); + } + } + if (!found) { + if (this.headerIt.hasNext()) { + this.currentHeader = this.headerIt.nextHeader().getValue(); + from = 0; + } else { + this.currentHeader = null; + } + } + } // while headers + + return found ? from : -1; + } + + + /** + * Determines the position of the next token separator. + * Because of multi-header joining rules, the end of a + * header value is a token separator. This method does + * therefore not need to iterate over headers. + * + * @param from the position in the current header at which to + * start the search + * + * @return the position of a token separator in the current header, + * or at the end + * + * @throws ParseException + * if a new token is found before a token separator. + * RFC 2616, section 2.1 explicitly requires a comma between + * tokens for <tt>#</tt>. + */ + protected int findTokenSeparator(int from) { + if (from < 0) { + throw new IllegalArgumentException + ("Search position must not be negative: " + from); + } + + boolean found = false; + final int to = this.currentHeader.length(); + while (!found && (from < to)) { + final char ch = this.currentHeader.charAt(from); + if (isTokenSeparator(ch)) { + found = true; + } else if (isWhitespace(ch)) { + from++; + } else if (isTokenChar(ch)) { + throw new ParseException + ("Tokens without separator (pos " + from + + "): " + this.currentHeader); + } else { + throw new ParseException + ("Invalid character after token (pos " + from + + "): " + this.currentHeader); + } + } + + return from; + } + + + /** + * Determines the ending position of the current token. + * This method will not leave the current header value, + * since the end of the header value is a token boundary. + * + * @param from the position of the first character of the token + * + * @return the position after the last character of the token. + * The behavior is undefined if <code>from</code> does not + * point to a token character in the current header value. + */ + protected int findTokenEnd(int from) { + if (from < 0) { + throw new IllegalArgumentException + ("Token start position must not be negative: " + from); + } + + final int to = this.currentHeader.length(); + int end = from+1; + while ((end < to) && isTokenChar(this.currentHeader.charAt(end))) { + end++; + } + + return end; + } + + + /** + * Checks whether a character is a token separator. + * RFC 2616, section 2.1 defines comma as the separator for + * <tt>#token</tt> sequences. The end of a header value will + * also separate tokens, but that is not a character check. + * + * @param ch the character to check + * + * @return <code>true</code> if the character is a token separator, + * <code>false</code> otherwise + */ + protected boolean isTokenSeparator(char ch) { + return (ch == ','); + } + + + /** + * Checks whether a character is a whitespace character. + * RFC 2616, section 2.2 defines space and horizontal tab as whitespace. + * The optional preceeding line break is irrelevant, since header + * continuation is handled transparently when parsing messages. + * + * @param ch the character to check + * + * @return <code>true</code> if the character is whitespace, + * <code>false</code> otherwise + */ + protected boolean isWhitespace(char ch) { + + // we do not use Character.isWhitspace(ch) here, since that allows + // many control characters which are not whitespace as per RFC 2616 + return ((ch == '\t') || Character.isSpaceChar(ch)); + } + + + /** + * Checks whether a character is a valid token character. + * Whitespace, control characters, and HTTP separators are not + * valid token characters. The HTTP specification (RFC 2616, section 2.2) + * defines tokens only for the US-ASCII character set, this + * method extends the definition to other character sets. + * + * @param ch the character to check + * + * @return <code>true</code> if the character is a valid token start, + * <code>false</code> otherwise + */ + protected boolean isTokenChar(char ch) { + + // common sense extension of ALPHA + DIGIT + if (Character.isLetterOrDigit(ch)) + return true; + + // common sense extension of CTL + if (Character.isISOControl(ch)) + return false; + + // no common sense extension for this + if (isHttpSeparator(ch)) + return false; + + // RFC 2616, section 2.2 defines a token character as + // "any CHAR except CTLs or separators". The controls + // and separators are included in the checks above. + // This will yield unexpected results for Unicode format characters. + // If that is a problem, overwrite isHttpSeparator(char) to filter + // out the false positives. + return true; + } + + + /** + * Checks whether a character is an HTTP separator. + * The implementation in this class checks only for the HTTP separators + * defined in RFC 2616, section 2.2. If you need to detect other + * separators beyond the US-ASCII character set, override this method. + * + * @param ch the character to check + * + * @return <code>true</code> if the character is an HTTP separator + */ + protected boolean isHttpSeparator(char ch) { + return (HTTP_SEPARATORS.indexOf(ch) >= 0); + } + + +} // class BasicTokenIterator + diff --git a/src/org/apache/http/message/BufferedHeader.java b/src/org/apache/http/message/BufferedHeader.java new file mode 100644 index 0000000..35c5cfc --- /dev/null +++ b/src/org/apache/http/message/BufferedHeader.java @@ -0,0 +1,133 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/BufferedHeader.java $ + * $Revision: 604625 $ + * $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.FormattedHeader; +import org.apache.http.HeaderElement; +import org.apache.http.ParseException; +import org.apache.http.util.CharArrayBuffer; + +/** + * This class represents a raw HTTP header whose content is parsed 'on demand' + * only when the header value needs to be consumed. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 604625 $ $Date: 2007-12-16 06:11:11 -0800 (Sun, 16 Dec 2007) $ + */ +public class BufferedHeader implements FormattedHeader, Cloneable { + + /** + * Header name. + */ + private final String name; + + /** + * The buffer containing the entire header line. + */ + private final CharArrayBuffer buffer; + + /** + * The beginning of the header value in the buffer + */ + private final int valuePos; + + + /** + * Creates a new header from a buffer. + * The name of the header will be parsed immediately, + * the value only if it is accessed. + * + * @param buffer the buffer containing the header to represent + * + * @throws ParseException in case of a parse error + */ + public BufferedHeader(final CharArrayBuffer buffer) + throws ParseException { + + super(); + if (buffer == null) { + throw new IllegalArgumentException + ("Char array buffer may not be null"); + } + int colon = buffer.indexOf(':'); + if (colon == -1) { + throw new ParseException + ("Invalid header: " + buffer.toString()); + } + String s = buffer.substringTrimmed(0, colon); + if (s.length() == 0) { + throw new ParseException + ("Invalid header: " + buffer.toString()); + } + this.buffer = buffer; + this.name = s; + this.valuePos = colon + 1; + } + + + public String getName() { + return this.name; + } + + public String getValue() { + return this.buffer.substringTrimmed(this.valuePos, this.buffer.length()); + } + + public HeaderElement[] getElements() throws ParseException { + ParserCursor cursor = new ParserCursor(0, this.buffer.length()); + cursor.updatePos(this.valuePos); + return BasicHeaderValueParser.DEFAULT + .parseElements(this.buffer, cursor); + } + + public int getValuePos() { + return this.valuePos; + } + + public CharArrayBuffer getBuffer() { + return this.buffer; + } + + public String toString() { + return this.buffer.toString(); + } + + public Object clone() throws CloneNotSupportedException { + // buffer is considered immutable + // no need to make a copy of it + return super.clone(); + } + +} diff --git a/src/org/apache/http/message/HeaderGroup.java b/src/org/apache/http/message/HeaderGroup.java new file mode 100644 index 0000000..4e40db1 --- /dev/null +++ b/src/org/apache/http/message/HeaderGroup.java @@ -0,0 +1,295 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/HeaderGroup.java $ + * $Revision: 659185 $ + * $Date: 2008-05-22 11:07:36 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.http.Header; +import org.apache.http.HeaderIterator; +import org.apache.http.util.CharArrayBuffer; + +/** + * A class for combining a set of headers. + * This class allows for multiple headers with the same name and + * keeps track of the order in which headers were added. + * + * @author Michael Becke + * + * @since 4.0 + */ +public class HeaderGroup implements Cloneable { + + /** The list of headers for this group, in the order in which they were added */ + private List headers; + + /** + * Constructor for HeaderGroup. + */ + public HeaderGroup() { + this.headers = new ArrayList(16); + } + + /** + * Removes any contained headers. + */ + public void clear() { + headers.clear(); + } + + /** + * Adds the given header to the group. The order in which this header was + * added is preserved. + * + * @param header the header to add + */ + public void addHeader(Header header) { + if (header == null) { + return; + } + headers.add(header); + } + + /** + * Removes the given header. + * + * @param header the header to remove + */ + public void removeHeader(Header header) { + if (header == null) { + return; + } + headers.remove(header); + } + + /** + * Replaces the first occurence of the header with the same name. If no header with + * the same name is found the given header is added to the end of the list. + * + * @param header the new header that should replace the first header with the same + * name if present in the list. + */ + public void updateHeader(Header header) { + if (header == null) { + return; + } + for (int i = 0; i < this.headers.size(); i++) { + Header current = (Header) this.headers.get(i); + if (current.getName().equalsIgnoreCase(header.getName())) { + this.headers.set(i, header); + return; + } + } + this.headers.add(header); + } + + /** + * Sets all of the headers contained within this group overriding any + * existing headers. The headers are added in the order in which they appear + * in the array. + * + * @param headers the headers to set + */ + public void setHeaders(Header[] headers) { + clear(); + if (headers == null) { + return; + } + for (int i = 0; i < headers.length; i++) { + this.headers.add(headers[i]); + } + } + + /** + * Gets a header representing all of the header values with the given name. + * If more that one header with the given name exists the values will be + * combined with a "," as per RFC 2616. + * + * <p>Header name comparison is case insensitive. + * + * @param name the name of the header(s) to get + * @return a header with a condensed value or <code>null</code> if no + * headers by the given name are present + */ + public Header getCondensedHeader(String name) { + Header[] headers = getHeaders(name); + + if (headers.length == 0) { + return null; + } else if (headers.length == 1) { + return headers[0]; + } else { + CharArrayBuffer valueBuffer = new CharArrayBuffer(128); + valueBuffer.append(headers[0].getValue()); + for (int i = 1; i < headers.length; i++) { + valueBuffer.append(", "); + valueBuffer.append(headers[i].getValue()); + } + + return new BasicHeader(name.toLowerCase(Locale.ENGLISH), valueBuffer.toString()); + } + } + + /** + * Gets all of the headers with the given name. The returned array + * maintains the relative order in which the headers were added. + * + * <p>Header name comparison is case insensitive. + * + * @param name the name of the header(s) to get + * + * @return an array of length >= 0 + */ + public Header[] getHeaders(String name) { + ArrayList headersFound = new ArrayList(); + + for (int i = 0; i < headers.size(); i++) { + Header header = (Header) headers.get(i); + if (header.getName().equalsIgnoreCase(name)) { + headersFound.add(header); + } + } + + return (Header[]) headersFound.toArray(new Header[headersFound.size()]); + } + + /** + * Gets the first header with the given name. + * + * <p>Header name comparison is case insensitive. + * + * @param name the name of the header to get + * @return the first header or <code>null</code> + */ + public Header getFirstHeader(String name) { + for (int i = 0; i < headers.size(); i++) { + Header header = (Header) headers.get(i); + if (header.getName().equalsIgnoreCase(name)) { + return header; + } + } + return null; + } + + /** + * Gets the last header with the given name. + * + * <p>Header name comparison is case insensitive. + * + * @param name the name of the header to get + * @return the last header or <code>null</code> + */ + public Header getLastHeader(String name) { + // start at the end of the list and work backwards + for (int i = headers.size() - 1; i >= 0; i--) { + Header header = (Header) headers.get(i); + if (header.getName().equalsIgnoreCase(name)) { + return header; + } + } + + return null; + } + + /** + * Gets all of the headers contained within this group. + * + * @return an array of length >= 0 + */ + public Header[] getAllHeaders() { + return (Header[]) headers.toArray(new Header[headers.size()]); + } + + /** + * Tests if headers with the given name are contained within this group. + * + * <p>Header name comparison is case insensitive. + * + * @param name the header name to test for + * @return <code>true</code> if at least one header with the name is + * contained, <code>false</code> otherwise + */ + public boolean containsHeader(String name) { + for (int i = 0; i < headers.size(); i++) { + Header header = (Header) headers.get(i); + if (header.getName().equalsIgnoreCase(name)) { + return true; + } + } + + return false; + } + + /** + * Returns an iterator over this group of headers. + * + * @return iterator over this group of headers. + * + * @since 4.0 + */ + public HeaderIterator iterator() { + return new BasicListHeaderIterator(this.headers, null); + } + + /** + * Returns an iterator over the headers with a given name in this group. + * + * @param name the name of the headers over which to iterate, or + * <code>null</code> for all headers + * + * @return iterator over some headers in this group. + * + * @since 4.0 + */ + public HeaderIterator iterator(final String name) { + return new BasicListHeaderIterator(this.headers, name); + } + + /** + * Returns a copy of this object + * + * @return copy of this object + */ + public HeaderGroup copy() { + HeaderGroup clone = new HeaderGroup(); + clone.headers.addAll(this.headers); + return clone; + } + + public Object clone() throws CloneNotSupportedException { + HeaderGroup clone = (HeaderGroup) super.clone(); + clone.headers = new ArrayList(this.headers); + return clone; + } + +} diff --git a/src/org/apache/http/message/HeaderValueFormatter.java b/src/org/apache/http/message/HeaderValueFormatter.java new file mode 100644 index 0000000..4f6351f --- /dev/null +++ b/src/org/apache/http/message/HeaderValueFormatter.java @@ -0,0 +1,141 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/HeaderValueFormatter.java $ + * $Revision: 571954 $ + * $Date: 2007-09-02 04:05:21 -0700 (Sun, 02 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.util.CharArrayBuffer; + + + +/** + * Interface for formatting elements of a header value. + * This is the complement to {@link HeaderValueParser}. + * Instances of this interface are expected to be stateless and thread-safe. + * + * <p> + * All formatting methods accept an optional buffer argument. + * If a buffer is passed in, the formatted element will be appended + * and the modified buffer is returned. If no buffer is passed in, + * a new buffer will be created and filled with the formatted element. + * In both cases, the caller is allowed to modify the returned buffer. + * </p> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 571954 $ + * + * @since 4.0 + */ +public interface HeaderValueFormatter { + + /** + * Formats an array of header elements. + * + * @param buffer the buffer to append to, or + * <code>null</code> to create a new buffer + * @param elems the header elements to format + * @param quote <code>true</code> to always format with quoted values, + * <code>false</code> to use quotes only when necessary + * + * @return a buffer with the formatted header elements. + * If the <code>buffer</code> argument was not <code>null</code>, + * that buffer will be used and returned. + */ + CharArrayBuffer formatElements(CharArrayBuffer buffer, + HeaderElement[] elems, + boolean quote) + ; + + + /** + * Formats one header element. + * + * @param buffer the buffer to append to, or + * <code>null</code> to create a new buffer + * @param elem the header element to format + * @param quote <code>true</code> to always format with quoted values, + * <code>false</code> to use quotes only when necessary + * + * @return a buffer with the formatted header element. + * If the <code>buffer</code> argument was not <code>null</code>, + * that buffer will be used and returned. + */ + CharArrayBuffer formatHeaderElement(CharArrayBuffer buffer, + HeaderElement elem, + boolean quote) + ; + + + + /** + * Formats the parameters of a header element. + * That's a list of name-value pairs, to be separated by semicolons. + * This method will <i>not</i> generate a leading semicolon. + * + * @param buffer the buffer to append to, or + * <code>null</code> to create a new buffer + * @param nvps the parameters (name-value pairs) to format + * @param quote <code>true</code> to always format with quoted values, + * <code>false</code> to use quotes only when necessary + * + * @return a buffer with the formatted parameters. + * If the <code>buffer</code> argument was not <code>null</code>, + * that buffer will be used and returned. + */ + CharArrayBuffer formatParameters(CharArrayBuffer buffer, + NameValuePair[] nvps, + boolean quote) + ; + + + /** + * Formats one name-value pair, where the value is optional. + * + * @param buffer the buffer to append to, or + * <code>null</code> to create a new buffer + * @param nvp the name-value pair to format + * @param quote <code>true</code> to always format with a quoted value, + * <code>false</code> to use quotes only when necessary + * + * @return a buffer with the formatted name-value pair. + * If the <code>buffer</code> argument was not <code>null</code>, + * that buffer will be used and returned. + */ + CharArrayBuffer formatNameValuePair(CharArrayBuffer buffer, + NameValuePair nvp, + boolean quote) + ; + +} + diff --git a/src/org/apache/http/message/HeaderValueParser.java b/src/org/apache/http/message/HeaderValueParser.java new file mode 100644 index 0000000..74bb93c --- /dev/null +++ b/src/org/apache/http/message/HeaderValueParser.java @@ -0,0 +1,214 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/HeaderValueParser.java $ + * $Revision: 589325 $ + * $Date: 2007-10-28 03:37:56 -0700 (Sun, 28 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.util.CharArrayBuffer; + + + +/** + * Interface for parsing header values into elements. + * Instances of this interface are expected to be stateless and thread-safe. + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 589325 $ $Date: 2007-10-28 03:37:56 -0700 (Sun, 28 Oct 2007) $ + * + * @since 4.0 + */ +public interface HeaderValueParser { + + /** + * Parses a header value into elements. + * Parse errors are indicated as <code>RuntimeException</code>. + * <p> + * Some HTTP headers (such as the set-cookie header) have values that + * can be decomposed into multiple elements. In order to be processed + * by this parser, such headers must be in the following form: + * </p> + * <pre> + * header = [ element ] *( "," [ element ] ) + * element = name [ "=" [ value ] ] *( ";" [ param ] ) + * param = name [ "=" [ value ] ] + * + * name = token + * value = ( token | quoted-string ) + * + * token = 1*<any char except "=", ",", ";", <"> and + * white space> + * quoted-string = <"> *( text | quoted-char ) <"> + * text = any char except <"> + * quoted-char = "\" char + * </pre> + * <p> + * Any amount of white space is allowed between any part of the + * header, element or param and is ignored. A missing value in any + * element or param will be stored as the empty {@link String}; + * if the "=" is also missing <var>null</var> will be stored instead. + * </p> + * + * @param buffer buffer holding the header value to parse + * @param cursor the parser cursor containing the current position and + * the bounds within the buffer for the parsing operation + * + * @return an array holding all elements of the header value + * + * @throws ParseException in case of a parse error + */ + HeaderElement[] parseElements( + CharArrayBuffer buffer, + ParserCursor cursor) throws ParseException; + + /** + * Parses a single header element. + * A header element consist of a semicolon-separate list + * of name=value definitions. + * + * @param buffer buffer holding the element to parse + * @param cursor the parser cursor containing the current position and + * the bounds within the buffer for the parsing operation + * + * @return the parsed element + * + * @throws ParseException in case of a parse error + */ + HeaderElement parseHeaderElement( + CharArrayBuffer buffer, + ParserCursor cursor) throws ParseException; + + /** + * Parses a list of name-value pairs. + * These lists are used to specify parameters to a header element. + * Parse errors are indicated as <code>RuntimeException</code>. + * <p> + * This method comforms to the generic grammar and formatting rules + * outlined in the + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2" + * >Section 2.2</a> + * and + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6" + * >Section 3.6</a> + * of + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>. + * </p> + * <h>2.2 Basic Rules</h> + * <p> + * The following rules are used throughout this specification to + * describe basic parsing constructs. + * The US-ASCII coded character set is defined by ANSI X3.4-1986. + * </p> + * <pre> + * OCTET = <any 8-bit sequence of data> + * CHAR = <any US-ASCII character (octets 0 - 127)> + * UPALPHA = <any US-ASCII uppercase letter "A".."Z"> + * LOALPHA = <any US-ASCII lowercase letter "a".."z"> + * ALPHA = UPALPHA | LOALPHA + * DIGIT = <any US-ASCII digit "0".."9"> + * CTL = <any US-ASCII control character + * (octets 0 - 31) and DEL (127)> + * CR = <US-ASCII CR, carriage return (13)> + * LF = <US-ASCII LF, linefeed (10)> + * SP = <US-ASCII SP, space (32)> + * HT = <US-ASCII HT, horizontal-tab (9)> + * <"> = <US-ASCII double-quote mark (34)> + * </pre> + * <p> + * Many HTTP/1.1 header field values consist of words separated + * by LWS or special characters. These special characters MUST be + * in a quoted string to be used within + * a parameter value (as defined in section 3.6). + * <p> + * <pre> + * token = 1*<any CHAR except CTLs or separators> + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + * </pre> + * <p> + * A string of text is parsed as a single word if it is quoted using + * double-quote marks. + * </p> + * <pre> + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = <any TEXT except <">> + * </pre> + * <p> + * The backslash character ("\") MAY be used as a single-character + * quoting mechanism only within quoted-string and comment constructs. + * </p> + * <pre> + * quoted-pair = "\" CHAR + * </pre> + * <h>3.6 Transfer Codings</h> + * <p> + * Parameters are in the form of attribute/value pairs. + * </p> + * <pre> + * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + * </pre> + * + * @param buffer buffer holding the name-value list to parse + * @param cursor the parser cursor containing the current position and + * the bounds within the buffer for the parsing operation + * + * @return an array holding all items of the name-value list + * + * @throws ParseException in case of a parse error + */ + NameValuePair[] parseParameters( + CharArrayBuffer buffer, + ParserCursor cursor) throws ParseException; + + + /** + * Parses a name=value specification, where the = and value are optional. + * + * @param buffer the buffer holding the name-value pair to parse + * @param cursor the parser cursor containing the current position and + * the bounds within the buffer for the parsing operation + * + * @return the name-value pair, where the value is <code>null</code> + * if no value is specified + */ + NameValuePair parseNameValuePair( + CharArrayBuffer buffer, + ParserCursor cursor) throws ParseException; + +} + diff --git a/src/org/apache/http/message/LineFormatter.java b/src/org/apache/http/message/LineFormatter.java new file mode 100644 index 0000000..ccc603c --- /dev/null +++ b/src/org/apache/http/message/LineFormatter.java @@ -0,0 +1,153 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/LineFormatter.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.StatusLine; +import org.apache.http.Header; +import org.apache.http.util.CharArrayBuffer; + + +/** + * Interface for formatting elements of the HEAD section of an HTTP message. + * This is the complement to {@link LineParser}. + * There are individual methods for formatting a request line, a + * status line, or a header line. The formatting does <i>not</i> include the + * trailing line break sequence CR-LF. + * Instances of this interface are expected to be stateless and thread-safe. + * + * <p> + * The formatted lines are returned in memory, the formatter does not depend + * on any specific IO mechanism. + * In order to avoid unnecessary creation of temporary objects, + * a buffer can be passed as argument to all formatting methods. + * The implementation may or may not actually use that buffer for formatting. + * If it is used, the buffer will first be cleared by the + * <code>formatXXX</code> methods. + * The argument buffer can always be re-used after the call. The buffer + * returned as the result, if it is different from the argument buffer, + * MUST NOT be modified. + * </p> + * + * + * @author <a href="mailto:rolandw AT apache.org">Roland Weber</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 573864 $ $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * @since 4.0 + */ +public interface LineFormatter { + + + + /** + * Formats a protocol version. + * This method does <i>not</i> follow the general contract for + * <code>buffer</code> arguments. + * It does <i>not</i> clear the argument buffer, but appends instead. + * The returned buffer can always be modified by the caller. + * Because of these differing conventions, it is not named + * <code>formatProtocolVersion</code>. + * + * @param buffer a buffer to which to append, or <code>null</code> + * @param version the protocol version to format + * + * @return a buffer with the formatted protocol version appended. + * The caller is allowed to modify the result buffer. + * If the <code>buffer</code> argument is not <code>null</code>, + * the returned buffer is the argument buffer. + */ + CharArrayBuffer appendProtocolVersion(CharArrayBuffer buffer, + ProtocolVersion version) + ; + + + /** + * Formats a request line. + * + * @param buffer a buffer available for formatting, or + * <code>null</code>. + * The buffer will be cleared before use. + * @param reqline the request line to format + * + * @return the formatted request line + */ + CharArrayBuffer formatRequestLine(CharArrayBuffer buffer, + RequestLine reqline) + ; + + + /** + * Formats a status line. + * + * @param buffer a buffer available for formatting, or + * <code>null</code>. + * The buffer will be cleared before use. + * @param statline the status line to format + * + * @return the formatted status line + * + * @throws ParseException in case of a parse error + */ + CharArrayBuffer formatStatusLine(CharArrayBuffer buffer, + StatusLine statline) + ; + + + /** + * Formats a header. + * Due to header continuation, the result may be multiple lines. + * In order to generate well-formed HTTP, the lines in the result + * must be separated by the HTTP line break sequence CR-LF. + * There is <i>no</i> trailing CR-LF in the result. + * <br/> + * See the class comment for details about the buffer argument. + * + * @param buffer a buffer available for formatting, or + * <code>null</code>. + * The buffer will be cleared before use. + * @param header the header to format + * + * @return a buffer holding the formatted header, never <code>null</code>. + * The returned buffer may be different from the argument buffer. + * + * @throws ParseException in case of a parse error + */ + CharArrayBuffer formatHeader(CharArrayBuffer buffer, + Header header) + ; + +} diff --git a/src/org/apache/http/message/LineParser.java b/src/org/apache/http/message/LineParser.java new file mode 100644 index 0000000..d1bcd15 --- /dev/null +++ b/src/org/apache/http/message/LineParser.java @@ -0,0 +1,156 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/LineParser.java $ + * $Revision: 589374 $ + * $Date: 2007-10-28 09:25:07 -0700 (Sun, 28 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + + +import org.apache.http.ProtocolVersion; +import org.apache.http.ParseException; +import org.apache.http.RequestLine; +import org.apache.http.StatusLine; +import org.apache.http.Header; +import org.apache.http.util.CharArrayBuffer; + + +/** + * Interface for parsing lines in the HEAD section of an HTTP message. + * There are individual methods for parsing a request line, a + * status line, or a header line. + * The lines to parse are passed in memory, the parser does not depend + * on any specific IO mechanism. + * Instances of this interface are expected to be stateless and thread-safe. + * + * @author <a href="mailto:rolandw AT apache.org">Roland Weber</a> + * + * + * <!-- empty lines above to avoid 'svn diff' context problems --> + * @version $Revision: 589374 $ $Date: 2007-10-28 09:25:07 -0700 (Sun, 28 Oct 2007) $ + * + * @since 4.0 + */ +public interface LineParser { + + + /** + * Parses the textual representation of a protocol version. + * This is needed for parsing request lines (last element) + * as well as status lines (first element). + * + * @param buffer a buffer holding the protocol version to parse + * @param cursor the parser cursor containing the current position and + * the bounds within the buffer for the parsing operation + * + * @return the parsed protocol version + * + * @throws ParseException in case of a parse error + */ + ProtocolVersion parseProtocolVersion( + CharArrayBuffer buffer, + ParserCursor cursor) throws ParseException; + + + /** + * Checks whether there likely is a protocol version in a line. + * This method implements a <i>heuristic</i> to check for a + * likely protocol version specification. It does <i>not</i> + * guarantee that {@link #parseProtocolVersion} would not + * detect a parse error. + * This can be used to detect garbage lines before a request + * or status line. + * + * @param buffer a buffer holding the line to inspect + * @param cursor the cursor at which to check for a protocol version, or + * negative for "end of line". Whether the check tolerates + * whitespace before or after the protocol version is + * implementation dependent. + * + * @return <code>true</code> if there is a protocol version at the + * argument index (possibly ignoring whitespace), + * <code>false</code> otherwise + */ + boolean hasProtocolVersion( + CharArrayBuffer buffer, + ParserCursor cursor); + + + /** + * Parses a request line. + * + * @param buffer a buffer holding the line to parse + * @param cursor the parser cursor containing the current position and + * the bounds within the buffer for the parsing operation + * + * @return the parsed request line + * + * @throws ParseException in case of a parse error + */ + RequestLine parseRequestLine( + CharArrayBuffer buffer, + ParserCursor cursor) throws ParseException; + + + /** + * Parses a status line. + * + * @param buffer a buffer holding the line to parse + * @param cursor the parser cursor containing the current position and + * the bounds within the buffer for the parsing operation + * + * @return the parsed status line + * + * @throws ParseException in case of a parse error + */ + StatusLine parseStatusLine( + CharArrayBuffer buffer, + ParserCursor cursor) throws ParseException; + + + /** + * Creates a header from a line. + * The full header line is expected here. Header continuation lines + * must be joined by the caller before invoking this method. + * + * @param buffer a buffer holding the full header line. + * This buffer MUST NOT be re-used afterwards, since + * the returned object may reference the contents later. + * + * @return the header in the argument buffer. + * The returned object MAY be a wrapper for the argument buffer. + * The argument buffer MUST NOT be re-used or changed afterwards. + * + * @throws ParseException in case of a parse error + */ + Header parseHeader(CharArrayBuffer buffer) + throws ParseException + ; + + +} diff --git a/src/org/apache/http/message/ParserCursor.java b/src/org/apache/http/message/ParserCursor.java new file mode 100644 index 0000000..d030675 --- /dev/null +++ b/src/org/apache/http/message/ParserCursor.java @@ -0,0 +1,102 @@ +/* + * $HeadURL:https://svn.apache.org/repos/asf/jakarta/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/ParserCursor.java $ + * $Revision:589325 $ + * $Date:2007-10-28 11:37:56 +0100 (Sun, 28 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.message; + +import org.apache.http.util.CharArrayBuffer; + +/** + * This class represents a context of a parsing operation: + * <ul> + * <li>the current position the parsing operation is expected to start at</li> + * <li>the bounds limiting the scope of the parsing operation</li> + * </ul> + * + * @author <a href="mailto:oleg at ural.com">Oleg Kalnichevski</a> + */ +public class ParserCursor { + + private final int lowerBound; + private final int upperBound; + private int pos; + + public ParserCursor(int lowerBound, int upperBound) { + super(); + if (lowerBound < 0) { + throw new IndexOutOfBoundsException("Lower bound cannot be negative"); + } + if (lowerBound > upperBound) { + throw new IndexOutOfBoundsException("Lower bound cannot be greater then upper bound"); + } + this.lowerBound = lowerBound; + this.upperBound = upperBound; + this.pos = lowerBound; + } + + public int getLowerBound() { + return this.lowerBound; + } + + public int getUpperBound() { + return this.upperBound; + } + + public int getPos() { + return this.pos; + } + + public void updatePos(int pos) { + if (pos < this.lowerBound) { + throw new IndexOutOfBoundsException(); + } + if (pos > this.upperBound) { + throw new IndexOutOfBoundsException(); + } + this.pos = pos; + } + + public boolean atEnd() { + return this.pos >= this.upperBound; + } + + public String toString() { + CharArrayBuffer buffer = new CharArrayBuffer(16); + buffer.append('['); + buffer.append(Integer.toString(this.lowerBound)); + buffer.append('>'); + buffer.append(Integer.toString(this.pos)); + buffer.append('>'); + buffer.append(Integer.toString(this.upperBound)); + buffer.append(']'); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/message/package.html b/src/org/apache/http/message/package.html new file mode 100644 index 0000000..88ee617 --- /dev/null +++ b/src/org/apache/http/message/package.html @@ -0,0 +1,49 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/message/package.html $ + * $Revision: 539755 $ + * $Date: 2007-05-19 07:05:02 -0700 (Sat, 19 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +A selection of HTTP +{@link org.apache.http.message.AbstractHttpMessage message} +implementations. + +There are basic implementations for HTTP requests +{@link org.apache.http.message.BasicHttpEntityEnclosingRequest with} +and {@link org.apache.http.message.BasicHttpRequest without} +an entity, and for +{@link org.apache.http.message.BasicHttpResponse responses}. + + +</body> +</html> diff --git a/src/org/apache/http/package.html b/src/org/apache/http/package.html new file mode 100644 index 0000000..e9647b4 --- /dev/null +++ b/src/org/apache/http/package.html @@ -0,0 +1,51 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/package.html $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The core interfaces and classes of the HTTP components. + +These deal with the fundamental things required for using the +HTTP protocol, such as representing a +{@link org.apache.http.HttpMessage message} including it's +{@link org.apache.http.Header headers} and optional +{@link org.apache.http.HttpEntity entity}, and +{@link org.apache.http.HttpConnection connections} +over which messages are sent. In order to prepare messages +before sending or after receiving, there are interceptors for +{@link org.apache.http.HttpRequestInterceptor requests} and +{@link org.apache.http.HttpResponseInterceptor responses}. + +</body> +</html> diff --git a/src/org/apache/http/params/AbstractHttpParams.java b/src/org/apache/http/params/AbstractHttpParams.java new file mode 100644 index 0000000..91631fc --- /dev/null +++ b/src/org/apache/http/params/AbstractHttpParams.java @@ -0,0 +1,116 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/AbstractHttpParams.java $ + * $Revision: 542224 $ + * $Date: 2007-05-28 06:34:04 -0700 (Mon, 28 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +import org.apache.http.params.HttpParams; + + +/** + * Abstract base class for parameter collections. + * Type specific setters and getters are mapped to the abstract, + * generic getters and setters. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 542224 $ + */ +public abstract class AbstractHttpParams implements HttpParams { + + /** + * Instantiates parameters. + */ + protected AbstractHttpParams() { + super(); + } + + public long getLongParameter(final String name, long defaultValue) { + Object param = getParameter(name); + if (param == null) { + return defaultValue; + } + return ((Long)param).longValue(); + } + + public HttpParams setLongParameter(final String name, long value) { + setParameter(name, new Long(value)); + return this; + } + + public int getIntParameter(final String name, int defaultValue) { + Object param = getParameter(name); + if (param == null) { + return defaultValue; + } + return ((Integer)param).intValue(); + } + + public HttpParams setIntParameter(final String name, int value) { + setParameter(name, new Integer(value)); + return this; + } + + public double getDoubleParameter(final String name, double defaultValue) { + Object param = getParameter(name); + if (param == null) { + return defaultValue; + } + return ((Double)param).doubleValue(); + } + + public HttpParams setDoubleParameter(final String name, double value) { + setParameter(name, new Double(value)); + return this; + } + + public boolean getBooleanParameter(final String name, boolean defaultValue) { + Object param = getParameter(name); + if (param == null) { + return defaultValue; + } + return ((Boolean)param).booleanValue(); + } + + public HttpParams setBooleanParameter(final String name, boolean value) { + setParameter(name, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + public boolean isParameterTrue(final String name) { + return getBooleanParameter(name, false); + } + + public boolean isParameterFalse(final String name) { + return !getBooleanParameter(name, false); + } + +} // class AbstractHttpParams diff --git a/src/org/apache/http/params/BasicHttpParams.java b/src/org/apache/http/params/BasicHttpParams.java new file mode 100644 index 0000000..70e6605 --- /dev/null +++ b/src/org/apache/http/params/BasicHttpParams.java @@ -0,0 +1,160 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/BasicHttpParams.java $ + * $Revision: 610464 $ + * $Date: 2008-01-09 09:10:55 -0800 (Wed, 09 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +import java.io.Serializable; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; + +import org.apache.http.params.HttpParams; + +/** + * This class represents a collection of HTTP protocol parameters. + * Protocol parameters may be linked together to form a hierarchy. + * If a particular parameter value has not been explicitly defined + * in the collection itself, its value will be drawn from the parent + * collection of parameters. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 610464 $ + */ +public final class BasicHttpParams extends AbstractHttpParams + implements Serializable, Cloneable { + + private static final long serialVersionUID = -7086398485908701455L; + + /** Map of HTTP parameters that this collection contains. */ + private HashMap parameters; + + public BasicHttpParams() { + super(); + } + + public Object getParameter(final String name) { + // See if the parameter has been explicitly defined + Object param = null; + if (this.parameters != null) { + param = this.parameters.get(name); + } + return param; + } + + public HttpParams setParameter(final String name, final Object value) { + if (this.parameters == null) { + this.parameters = new HashMap(); + } + this.parameters.put(name, value); + return this; + } + + public boolean removeParameter(String name) { + if (this.parameters == null) { + return false; + } + //this is to avoid the case in which the key has a null value + if (this.parameters.containsKey(name)) { + this.parameters.remove(name); + return true; + } else { + return false; + } + } + + + /** + * Assigns the value to all the parameter with the given names + * + * @param names array of parameter name + * @param value parameter value + */ + public void setParameters(final String[] names, final Object value) { + for (int i = 0; i < names.length; i++) { + setParameter(names[i], value); + } + } + + public boolean isParameterSet(final String name) { + return getParameter(name) != null; + } + + public boolean isParameterSetLocally(final String name) { + return this.parameters != null && this.parameters.get(name) != null; + } + + /** + * Removes all parameters from this collection. + */ + public void clear() { + this.parameters = null; + } + + /** + * Creates a copy of these parameters. + * The implementation here instantiates {@link BasicHttpParams}, + * then calls {@link #copyParams(HttpParams)} to populate the copy. + * + * @return a new set of params holding a copy of the + * <i>local</i> parameters in this object. + */ + public HttpParams copy() { + BasicHttpParams clone = new BasicHttpParams(); + copyParams(clone); + return clone; + } + + public Object clone() throws CloneNotSupportedException { + BasicHttpParams clone = (BasicHttpParams) super.clone(); + copyParams(clone); + return clone; + } + + /** + * Copies the locally defined parameters to the argument parameters. + * This method is called from {@link #copy()}. + * + * @param target the parameters to which to copy + */ + protected void copyParams(HttpParams target) { + if (this.parameters == null) + return; + + Iterator iter = parameters.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry me = (Map.Entry) iter.next(); + if (me.getKey() instanceof String) + target.setParameter((String)me.getKey(), me.getValue()); + } + } + +} diff --git a/src/org/apache/http/params/CoreConnectionPNames.java b/src/org/apache/http/params/CoreConnectionPNames.java new file mode 100644 index 0000000..a2dec8b --- /dev/null +++ b/src/org/apache/http/params/CoreConnectionPNames.java @@ -0,0 +1,131 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/CoreConnectionPNames.java $ + * $Revision: 576077 $ + * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + + +/** + * Defines parameter names for connections in HttpCore. + * + * @version $Revision: 576077 $ + * + * @since 4.0 + */ +public interface CoreConnectionPNames { + + /** + * Defines the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the + * timeout for waiting for data. A timeout value of zero is interpreted as an infinite + * timeout. This value is used when no socket timeout is set in the + * method parameters. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + * @see java.net.SocketOptions#SO_TIMEOUT + */ + public static final String SO_TIMEOUT = "http.socket.timeout"; + + /** + * Determines whether Nagle's algorithm is to be used. The Nagle's algorithm + * tries to conserve bandwidth by minimizing the number of segments that are + * sent. When applications wish to decrease network latency and increase + * performance, they can disable Nagle's algorithm (that is enable TCP_NODELAY). + * Data will be sent earlier, at the cost of an increase in bandwidth consumption. + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + * @see java.net.SocketOptions#TCP_NODELAY + */ + public static final String TCP_NODELAY = "http.tcp.nodelay"; + + /** + * Determines the size of the internal socket buffer used to buffer data + * while receiving / transmitting HTTP messages. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + */ + public static final String SOCKET_BUFFER_SIZE = "http.socket.buffer-size"; + + /** + * Sets SO_LINGER with the specified linger time in seconds. The maximum timeout + * value is platform specific. Value <tt>0</tt> implies that the option is disabled. + * Value <tt>-1</tt> implies that the JRE default is used. The setting only affects + * socket close. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + * @see java.net.SocketOptions#SO_LINGER + */ + public static final String SO_LINGER = "http.socket.linger"; + + /** + * Determines the timeout until a connection is etablished. A value of zero + * means the timeout is not used. The default value is zero. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + */ + public static final String CONNECTION_TIMEOUT = "http.connection.timeout"; + + /** + * Determines whether stale connection check is to be used. Disabling + * stale connection check may result in slight performance improvement + * at the risk of getting an I/O error when executing a request over a + * connection that has been closed at the server side. + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + */ + public static final String STALE_CONNECTION_CHECK = "http.connection.stalecheck"; + + /** + * Determines the maximum line length limit. If set to a positive value, any HTTP + * line exceeding this limit will cause an IOException. A negative or zero value + * will effectively disable the check. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + */ + public static final String MAX_LINE_LENGTH = "http.connection.max-line-length"; + + /** + * Determines the maximum HTTP header count allowed. If set to a positive value, + * the number of HTTP headers received from the data stream exceeding this limit + * will cause an IOException. A negative or zero value will effectively disable + * the check. + * <p> + * This parameter expects a value of type {@link Integer}. + * </p> + */ + public static final String MAX_HEADER_COUNT = "http.connection.max-header-count"; + +} diff --git a/src/org/apache/http/params/CoreProtocolPNames.java b/src/org/apache/http/params/CoreProtocolPNames.java new file mode 100644 index 0000000..a42c5de --- /dev/null +++ b/src/org/apache/http/params/CoreProtocolPNames.java @@ -0,0 +1,132 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/CoreProtocolPNames.java $ + * $Revision: 576077 $ + * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + + +/** + * Defines parameter names for protocol execution in HttpCore. + * + * @version $Revision: 576077 $ + * + * @since 4.0 + */ +public interface CoreProtocolPNames { + + /** + * Defines the {@link org.apache.http.ProtocolVersion protocol version} + * used per default. + * <p> + * This parameter expects a value of type + * {@link org.apache.http.ProtocolVersion}. + * </p> + */ + public static final String PROTOCOL_VERSION = "http.protocol.version"; + + /** + * Defines the charset to be used for encoding HTTP protocol elements. + * <p> + * This parameter expects a value of type {@link String}. + * </p> + */ + public static final String HTTP_ELEMENT_CHARSET = "http.protocol.element-charset"; + + /** + * Defines the charset to be used per default for encoding content body. + * <p> + * This parameter expects a value of type {@link String}. + * </p> + */ + public static final String HTTP_CONTENT_CHARSET = "http.protocol.content-charset"; + + /** + * Defines the content of the <tt>User-Agent</tt> header. + * <p> + * This parameter expects a value of type {@link String}. + * </p> + */ + public static final String USER_AGENT = "http.useragent"; + + /** + * Defines the content of the <tt>Server</tt> header. + * <p> + * This parameter expects a value of type {@link String}. + * </p> + */ + public static final String ORIGIN_SERVER = "http.origin-server"; + + /** + * Defines whether responses with an invalid <tt>Transfer-Encoding</tt> header should be + * rejected. + * <p> + * This parameter expects a value of type {@link Boolean}. + * </p> + */ + public static final String STRICT_TRANSFER_ENCODING = "http.protocol.strict-transfer-encoding"; + + /** + * <p> + * Activates 'Expect: 100-Continue' handshake for the + * entity enclosing methods. The purpose of the 'Expect: 100-Continue' + * handshake to allow a client that is sending a request message with + * a request body to determine if the origin server is willing to + * accept the request (based on the request headers) before the client + * sends the request body. + * </p> + * + * <p> + * The use of the 'Expect: 100-continue' handshake can result in + * noticable peformance improvement for entity enclosing requests + * (such as POST and PUT) that require the target server's + * authentication. + * </p> + * + * <p> + * 'Expect: 100-continue' handshake should be used with + * caution, as it may cause problems with HTTP servers and + * proxies that do not support HTTP/1.1 protocol. + * </p> + * + * This parameter expects a value of type {@link Boolean}. + */ + public static final String USE_EXPECT_CONTINUE = "http.protocol.expect-continue"; + + /** + * <p> + * Defines the maximum period of time in milliseconds the client should spend + * waiting for a 100-continue response. + * </p> + * + * This parameter expects a value of type {@link Integer}. + */ + public static final String WAIT_FOR_CONTINUE = "http.protocol.wait-for-continue"; + +} diff --git a/src/org/apache/http/params/DefaultedHttpParams.java b/src/org/apache/http/params/DefaultedHttpParams.java new file mode 100644 index 0000000..ce33247 --- /dev/null +++ b/src/org/apache/http/params/DefaultedHttpParams.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/DefaultedHttpParams.java $ + * $Revision: 610763 $ + * $Date: 2008-01-10 04:01:13 -0800 (Thu, 10 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +import org.apache.http.params.HttpParams; + +/** + * {@link HttpParams} implementation that delegates resolution of a parameter + * to the given default {@link HttpParams} instance if the parameter is not + * present in the local one. The state of the local collection can be mutated, + * whereas the default collection is treated as read-only. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 610763 $ + */ +public final class DefaultedHttpParams extends AbstractHttpParams { + + private final HttpParams local; + private final HttpParams defaults; + + public DefaultedHttpParams(final HttpParams local, final HttpParams defaults) { + super(); + if (local == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.local = local; + this.defaults = defaults; + } + + /** + * Creates a copy of the local collection with the same default + */ + public HttpParams copy() { + HttpParams clone = this.local.copy(); + return new DefaultedHttpParams(clone, this.defaults); + } + + /** + * Retrieves the value of the parameter from the local collection and, if the + * parameter is not set locally, delegates its resolution to the default + * collection. + */ + public Object getParameter(final String name) { + Object obj = this.local.getParameter(name); + if (obj == null && this.defaults != null) { + obj = this.defaults.getParameter(name); + } + return obj; + } + + /** + * Attempts to remove the parameter from the local collection. This method + * <i>does not</i> modify the default collection. + */ + public boolean removeParameter(final String name) { + return this.local.removeParameter(name); + } + + /** + * Sets the parameter in the local collection. This method <i>does not</i> + * modify the default collection. + */ + public HttpParams setParameter(final String name, final Object value) { + return this.local.setParameter(name, value); + } + + public HttpParams getDefaults() { + return this.defaults; + } + +} diff --git a/src/org/apache/http/params/HttpAbstractParamBean.java b/src/org/apache/http/params/HttpAbstractParamBean.java new file mode 100644 index 0000000..8701a99 --- /dev/null +++ b/src/org/apache/http/params/HttpAbstractParamBean.java @@ -0,0 +1,44 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpAbstractParamBean.java $ + * $Revision: 593937 $ + * $Date: 2007-11-11 10:44:12 -0800 (Sun, 11 Nov 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +public abstract class HttpAbstractParamBean { + + protected final HttpParams params; + + public HttpAbstractParamBean (final HttpParams params) { + if (params == null) + throw new IllegalArgumentException("HTTP parameters may not be null"); + this.params = params; + } + +} diff --git a/src/org/apache/http/params/HttpConnectionParamBean.java b/src/org/apache/http/params/HttpConnectionParamBean.java new file mode 100644 index 0000000..0b61346 --- /dev/null +++ b/src/org/apache/http/params/HttpConnectionParamBean.java @@ -0,0 +1,64 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpConnectionParamBean.java $ + * $Revision: 593937 $ + * $Date: 2007-11-11 10:44:12 -0800 (Sun, 11 Nov 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +public class HttpConnectionParamBean extends HttpAbstractParamBean { + + public HttpConnectionParamBean (final HttpParams params) { + super(params); + } + + public void setSoTimeout (int soTimeout) { + HttpConnectionParams.setSoTimeout(params, soTimeout); + } + + public void setTcpNoDelay (boolean tcpNoDelay) { + HttpConnectionParams.setTcpNoDelay(params, tcpNoDelay); + } + + public void setSocketBufferSize (int socketBufferSize) { + HttpConnectionParams.setSocketBufferSize(params, socketBufferSize); + } + + public void setLinger (int linger) { + HttpConnectionParams.setLinger(params, linger); + } + + public void setConnectionTimeout (int connectionTimeout) { + HttpConnectionParams.setConnectionTimeout(params, connectionTimeout); + } + + public void setStaleCheckingEnabled (boolean staleCheckingEnabled) { + HttpConnectionParams.setStaleCheckingEnabled(params, staleCheckingEnabled); + } + +} diff --git a/src/org/apache/http/params/HttpConnectionParams.java b/src/org/apache/http/params/HttpConnectionParams.java new file mode 100644 index 0000000..7918a66 --- /dev/null +++ b/src/org/apache/http/params/HttpConnectionParams.java @@ -0,0 +1,224 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpConnectionParams.java $ + * $Revision: 576089 $ + * $Date: 2007-09-16 05:39:56 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +/** + * An adaptor for accessing connection parameters in {@link HttpParams}. + * <br/> + * Note that the <i>implements</i> relation to {@link CoreConnectionPNames} + * is for compatibility with existing application code only. References to + * the parameter names should use the interface, not this class. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 576089 $ + * + * @since 4.0 + */ +public final class HttpConnectionParams implements CoreConnectionPNames { + + /** + */ + private HttpConnectionParams() { + super(); + } + + /** + * Returns the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the + * timeout for waiting for data. A timeout value of zero is interpreted as an infinite + * timeout. This value is used when no socket timeout is set in the + * method parameters. + * + * @return timeout in milliseconds + */ + public static int getSoTimeout(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getIntParameter(CoreConnectionPNames.SO_TIMEOUT, 0); + } + + /** + * Sets the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the + * timeout for waiting for data. A timeout value of zero is interpreted as an infinite + * timeout. This value is used when no socket timeout is set in the + * method parameters. + * + * @param timeout Timeout in milliseconds + */ + public static void setSoTimeout(final HttpParams params, int timeout) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout); + + } + + /** + * Tests if Nagle's algorithm is to be used. + * + * @return <tt>true</tt> if the Nagle's algorithm is to NOT be used + * (that is enable TCP_NODELAY), <tt>false</tt> otherwise. + */ + public static boolean getTcpNoDelay(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getBooleanParameter + (CoreConnectionPNames.TCP_NODELAY, true); + } + + /** + * Determines whether Nagle's algorithm is to be used. The Nagle's algorithm + * tries to conserve bandwidth by minimizing the number of segments that are + * sent. When applications wish to decrease network latency and increase + * performance, they can disable Nagle's algorithm (that is enable TCP_NODELAY). + * Data will be sent earlier, at the cost of an increase in bandwidth consumption. + * + * @param value <tt>true</tt> if the Nagle's algorithm is to NOT be used + * (that is enable TCP_NODELAY), <tt>false</tt> otherwise. + */ + public static void setTcpNoDelay(final HttpParams params, boolean value) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, value); + } + + public static int getSocketBufferSize(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getIntParameter + (CoreConnectionPNames.SOCKET_BUFFER_SIZE, -1); + } + + public static void setSocketBufferSize(final HttpParams params, int size) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, size); + } + + /** + * Returns linger-on-close timeout. Value <tt>0</tt> implies that the option is + * disabled. Value <tt>-1</tt> implies that the JRE default is used. + * + * @return the linger-on-close timeout + */ + public static int getLinger(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getIntParameter(CoreConnectionPNames.SO_LINGER, -1); + } + + /** + * Returns linger-on-close timeout. This option disables/enables immediate return + * from a close() of a TCP Socket. Enabling this option with a non-zero Integer + * timeout means that a close() will block pending the transmission and + * acknowledgement of all data written to the peer, at which point the socket is + * closed gracefully. Value <tt>0</tt> implies that the option is + * disabled. Value <tt>-1</tt> implies that the JRE default is used. + * + * @param value the linger-on-close timeout + */ + public static void setLinger(final HttpParams params, int value) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setIntParameter(CoreConnectionPNames.SO_LINGER, value); + } + + /** + * Returns the timeout until a connection is etablished. A value of zero + * means the timeout is not used. The default value is zero. + * + * @return timeout in milliseconds. + */ + public static int getConnectionTimeout(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getIntParameter + (CoreConnectionPNames.CONNECTION_TIMEOUT, 0); + } + + /** + * Sets the timeout until a connection is etablished. A value of zero + * means the timeout is not used. The default value is zero. + * + * @param timeout Timeout in milliseconds. + */ + public static void setConnectionTimeout(final HttpParams params, int timeout) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setIntParameter + (CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); + } + + /** + * Tests whether stale connection check is to be used. Disabling + * stale connection check may result in slight performance improvement + * at the risk of getting an I/O error when executing a request over a + * connection that has been closed at the server side. + * + * @return <tt>true</tt> if stale connection check is to be used, + * <tt>false</tt> otherwise. + */ + public static boolean isStaleCheckingEnabled(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getBooleanParameter + (CoreConnectionPNames.STALE_CONNECTION_CHECK, true); + } + + /** + * Defines whether stale connection check is to be used. Disabling + * stale connection check may result in slight performance improvement + * at the risk of getting an I/O error when executing a request over a + * connection that has been closed at the server side. + * + * @param value <tt>true</tt> if stale connection check is to be used, + * <tt>false</tt> otherwise. + */ + public static void setStaleCheckingEnabled(final HttpParams params, boolean value) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setBooleanParameter + (CoreConnectionPNames.STALE_CONNECTION_CHECK, value); + } + +} diff --git a/src/org/apache/http/params/HttpParams.java b/src/org/apache/http/params/HttpParams.java new file mode 100644 index 0000000..ba901a2 --- /dev/null +++ b/src/org/apache/http/params/HttpParams.java @@ -0,0 +1,187 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpParams.java $ + * $Revision: 610763 $ + * $Date: 2008-01-10 04:01:13 -0800 (Thu, 10 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +/** + * Represents a collection of HTTP protocol and framework parameters. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 610763 $ + * + * @since 4.0 + */ +public interface HttpParams { + + /** + * Obtains the value of the given parameter. + * + * @param name the parent name. + * + * @return an object that represents the value of the parameter, + * <code>null</code> if the parameter is not set or if it + * is explicitly set to <code>null</code> + * + * @see #setParameter(String, Object) + */ + Object getParameter(String name); + + /** + * Assigns the value to the parameter with the given name. + * + * @param name parameter name + * @param value parameter value + */ + HttpParams setParameter(String name, Object value); + + /** + * Creates a copy of these parameters. + * + * @return a new set of parameters holding the same values as this one + */ + HttpParams copy(); + + /** + * Removes the parameter with the specified name. + * + * @param name parameter name + * + * @return true if the parameter existed and has been removed, false else. + */ + boolean removeParameter(String name); + + /** + * Returns a {@link Long} parameter value with the given name. + * If the parameter is not explicitly set, the default value is returned. + * + * @param name the parent name. + * @param defaultValue the default value. + * + * @return a {@link Long} that represents the value of the parameter. + * + * @see #setLongParameter(String, long) + */ + long getLongParameter(String name, long defaultValue); + + /** + * Assigns a {@link Long} to the parameter with the given name + * + * @param name parameter name + * @param value parameter value + */ + HttpParams setLongParameter(String name, long value); + + /** + * Returns an {@link Integer} parameter value with the given name. + * If the parameter is not explicitly set, the default value is returned. + * + * @param name the parent name. + * @param defaultValue the default value. + * + * @return a {@link Integer} that represents the value of the parameter. + * + * @see #setIntParameter(String, int) + */ + int getIntParameter(String name, int defaultValue); + + /** + * Assigns an {@link Integer} to the parameter with the given name + * + * @param name parameter name + * @param value parameter value + */ + HttpParams setIntParameter(String name, int value); + + /** + * Returns a {@link Double} parameter value with the given name. + * If the parameter is not explicitly set, the default value is returned. + * + * @param name the parent name. + * @param defaultValue the default value. + * + * @return a {@link Double} that represents the value of the parameter. + * + * @see #setDoubleParameter(String, double) + */ + double getDoubleParameter(String name, double defaultValue); + + /** + * Assigns a {@link Double} to the parameter with the given name + * + * @param name parameter name + * @param value parameter value + */ + HttpParams setDoubleParameter(String name, double value); + + /** + * Returns a {@link Boolean} parameter value with the given name. + * If the parameter is not explicitly set, the default value is returned. + * + * @param name the parent name. + * @param defaultValue the default value. + * + * @return a {@link Boolean} that represents the value of the parameter. + * + * @see #setBooleanParameter(String, boolean) + */ + boolean getBooleanParameter(String name, boolean defaultValue); + + /** + * Assigns a {@link Boolean} to the parameter with the given name + * + * @param name parameter name + * @param value parameter value + */ + HttpParams setBooleanParameter(String name, boolean value); + + /** + * Checks if a boolean parameter is set to <code>true</code>. + * + * @param name parameter name + * + * @return <tt>true</tt> if the parameter is set to value <tt>true</tt>, + * <tt>false</tt> if it is not set or set to <code>false</code> + */ + boolean isParameterTrue(String name); + + /** + * Checks if a boolean parameter is not set or <code>false</code>. + * + * @param name parameter name + * + * @return <tt>true</tt> if the parameter is either not set or + * set to value <tt>false</tt>, + * <tt>false</tt> if it is set to <code>true</code> + */ + boolean isParameterFalse(String name); + +} diff --git a/src/org/apache/http/params/HttpProtocolParamBean.java b/src/org/apache/http/params/HttpProtocolParamBean.java new file mode 100644 index 0000000..6273430 --- /dev/null +++ b/src/org/apache/http/params/HttpProtocolParamBean.java @@ -0,0 +1,62 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpProtocolParamBean.java $ + * $Revision: 593937 $ + * $Date: 2007-11-11 10:44:12 -0800 (Sun, 11 Nov 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +import org.apache.http.HttpVersion; + +public class HttpProtocolParamBean extends HttpAbstractParamBean { + + public HttpProtocolParamBean (final HttpParams params) { + super(params); + } + + public void setHttpElementCharset (final String httpElementCharset) { + HttpProtocolParams.setHttpElementCharset(params, httpElementCharset); + } + + public void setContentCharset (final String contentCharset) { + HttpProtocolParams.setContentCharset(params, contentCharset); + } + + public void setVersion (final HttpVersion version) { + HttpProtocolParams.setVersion(params, version); + } + + public void setUserAgent (final String userAgent) { + HttpProtocolParams.setUserAgent(params, userAgent); + } + + public void setUseExpectContinue (boolean useExpectContinue) { + HttpProtocolParams.setUseExpectContinue(params, useExpectContinue); + } + +} diff --git a/src/org/apache/http/params/HttpProtocolParams.java b/src/org/apache/http/params/HttpProtocolParams.java new file mode 100644 index 0000000..71e9c67 --- /dev/null +++ b/src/org/apache/http/params/HttpProtocolParams.java @@ -0,0 +1,176 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpProtocolParams.java $ + * $Revision: 576089 $ + * $Date: 2007-09-16 05:39:56 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.params; + +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.protocol.HTTP; + +/** + * This class implements an adaptor around the {@link HttpParams} interface + * to simplify manipulation of the HTTP protocol specific parameters. + * <br/> + * Note that the <i>implements</i> relation to {@link CoreProtocolPNames} + * is for compatibility with existing application code only. References to + * the parameter names should use the interface, not this class. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 576089 $ + * + * @since 4.0 + * + * @see CoreProtocolPNames + */ +public final class HttpProtocolParams implements CoreProtocolPNames { + + /** + */ + private HttpProtocolParams() { + super(); + } + + /** + * Returns the charset to be used for writing HTTP headers. + * @return The charset + */ + public static String getHttpElementCharset(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + String charset = (String) params.getParameter + (CoreProtocolPNames.HTTP_ELEMENT_CHARSET); + if (charset == null) { + charset = HTTP.DEFAULT_PROTOCOL_CHARSET; + } + return charset; + } + + /** + * Sets the charset to be used for writing HTTP headers. + * @param charset The charset + */ + public static void setHttpElementCharset(final HttpParams params, final String charset) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET, charset); + } + + /** + * Returns the default charset to be used for writing content body, + * when no charset explicitly specified. + * @return The charset + */ + public static String getContentCharset(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + String charset = (String) params.getParameter + (CoreProtocolPNames.HTTP_CONTENT_CHARSET); + if (charset == null) { + charset = HTTP.DEFAULT_CONTENT_CHARSET; + } + return charset; + } + + /** + * Sets the default charset to be used for writing content body, + * when no charset explicitly specified. + * @param charset The charset + */ + public static void setContentCharset(final HttpParams params, final String charset) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, charset); + } + + /** + * Returns {@link ProtocolVersion protocol version} to be used per default. + * + * @return {@link ProtocolVersion protocol version} + */ + public static ProtocolVersion getVersion(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + Object param = params.getParameter + (CoreProtocolPNames.PROTOCOL_VERSION); + if (param == null) { + return HttpVersion.HTTP_1_1; + } + return (ProtocolVersion)param; + } + + /** + * Assigns the {@link ProtocolVersion protocol version} to be used by the + * HTTP methods that this collection of parameters applies to. + * + * @param version the {@link ProtocolVersion protocol version} + */ + public static void setVersion(final HttpParams params, final ProtocolVersion version) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, version); + } + + public static String getUserAgent(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return (String) params.getParameter(CoreProtocolPNames.USER_AGENT); + } + + public static void setUserAgent(final HttpParams params, final String useragent) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setParameter(CoreProtocolPNames.USER_AGENT, useragent); + } + + public static boolean useExpectContinue(final HttpParams params) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + return params.getBooleanParameter + (CoreProtocolPNames.USE_EXPECT_CONTINUE, false); + } + + public static void setUseExpectContinue(final HttpParams params, boolean b) { + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + params.setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, b); + } +} diff --git a/src/org/apache/http/params/package.html b/src/org/apache/http/params/package.html new file mode 100644 index 0000000..3943053 --- /dev/null +++ b/src/org/apache/http/params/package.html @@ -0,0 +1,40 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/package.html $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +The parameterization framework for HTTP components. + +</body> +</html> diff --git a/src/org/apache/http/protocol/BasicHttpContext.java b/src/org/apache/http/protocol/BasicHttpContext.java new file mode 100644 index 0000000..9b4e2b3 --- /dev/null +++ b/src/org/apache/http/protocol/BasicHttpContext.java @@ -0,0 +1,95 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/BasicHttpContext.java $ + * $Revision: 654882 $ + * $Date: 2008-05-09 09:58:59 -0700 (Fri, 09 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation of the {@link HttpContext HttpContext}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 654882 $ + * + * @since 4.0 + */ +public class BasicHttpContext implements HttpContext { + + private final HttpContext parentContext; + private Map map = null; + + public BasicHttpContext() { + this(null); + } + + public BasicHttpContext(final HttpContext parentContext) { + super(); + this.parentContext = parentContext; + } + + public Object getAttribute(final String id) { + if (id == null) { + throw new IllegalArgumentException("Id may not be null"); + } + Object obj = null; + if (this.map != null) { + obj = this.map.get(id); + } + if (obj == null && this.parentContext != null) { + obj = this.parentContext.getAttribute(id); + } + return obj; + } + + public void setAttribute(final String id, final Object obj) { + if (id == null) { + throw new IllegalArgumentException("Id may not be null"); + } + if (this.map == null) { + this.map = new HashMap(); + } + this.map.put(id, obj); + } + + public Object removeAttribute(final String id) { + if (id == null) { + throw new IllegalArgumentException("Id may not be null"); + } + if (this.map != null) { + return this.map.remove(id); + } else { + return null; + } + } + +} diff --git a/src/org/apache/http/protocol/BasicHttpProcessor.java b/src/org/apache/http/protocol/BasicHttpProcessor.java new file mode 100644 index 0000000..3caec72 --- /dev/null +++ b/src/org/apache/http/protocol/BasicHttpProcessor.java @@ -0,0 +1,337 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/BasicHttpProcessor.java $ + * $Revision: 613298 $ + * $Date: 2008-01-18 14:09:22 -0800 (Fri, 18 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; + +/** + * Keeps lists of interceptors for processing requests and responses. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author Andrea Selva + * + * @version $Revision: 613298 $ + * + * @since 4.0 + */ +public final class BasicHttpProcessor implements + HttpProcessor, HttpRequestInterceptorList, HttpResponseInterceptorList, Cloneable { + + protected List requestInterceptors = null; + protected List responseInterceptors = null; + + + // non-Javadoc, see interface HttpRequestInterceptorList + public void addRequestInterceptor(final HttpRequestInterceptor itcp) { + + if (itcp == null) { + return; + } + if (this.requestInterceptors == null) { + this.requestInterceptors = new ArrayList(); + } + this.requestInterceptors.add(itcp); + } + + // non-Javadoc, see interface HttpRequestInterceptorList + public void addRequestInterceptor(final HttpRequestInterceptor itcp, + int index) { + if (index < 0) { + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + if (itcp == null) { + return; + } + + if (this.requestInterceptors == null) { + if (index > 0) { + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + this.requestInterceptors = new ArrayList(); + } + this.requestInterceptors.add(index, itcp); + } + + + public void addResponseInterceptor(HttpResponseInterceptor itcp, + int index) { + if (index < 0) { + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + if (itcp == null) { + return; + } + + if (this.responseInterceptors == null) { + if (index > 0) { + throw new IndexOutOfBoundsException(String.valueOf(index)); + } + this.responseInterceptors = new ArrayList(); + } + this.responseInterceptors.add(index, itcp); + } + + + // non-Javadoc, see interface HttpRequestInterceptorList + public void removeRequestInterceptorByClass(final Class clazz) { + if (this.requestInterceptors == null) { + return; + } + for (Iterator it = this.requestInterceptors.iterator(); + it.hasNext(); ) { + Object request = it.next(); + if (request.getClass().equals(clazz)) { + it.remove(); + } + } + } + + // non-Javadoc, see interface HttpResponseInterceptorList + public void removeResponseInterceptorByClass(final Class clazz) { + if (this.responseInterceptors == null) { + return; + } + for (Iterator it = this.responseInterceptors.iterator(); + it.hasNext(); ) { + Object request = it.next(); + if (request.getClass().equals(clazz)) { + it.remove(); + } + } + } + + /** + * Same as {@link #addRequestInterceptor(HttpRequestInterceptor) addRequestInterceptor}. + * + * @param interceptor the interceptor to add + */ + public final + void addInterceptor(final HttpRequestInterceptor interceptor) { + addRequestInterceptor(interceptor); + } + + public final + void addInterceptor(final HttpRequestInterceptor interceptor, + int index) { + addRequestInterceptor(interceptor, index); + } + + + // non-Javadoc, see interface HttpRequestInterceptorList + public int getRequestInterceptorCount() { + return (this.requestInterceptors == null) ? + 0 : this.requestInterceptors.size(); + } + + + // non-Javadoc, see interface HttpRequestInterceptorList + public HttpRequestInterceptor getRequestInterceptor(int index) { + + if ((this.requestInterceptors == null) || + (index < 0) || (index >= this.requestInterceptors.size())) + return null; + + return (HttpRequestInterceptor) this.requestInterceptors.get(index); + } + + + // non-Javadoc, see interface HttpRequestInterceptorList + public void clearRequestInterceptors() { + this.requestInterceptors = null; + } + + + + // non-Javadoc, see interface HttpResponseInterceptorList + public void addResponseInterceptor(final HttpResponseInterceptor itcp) { + if (itcp == null) { + return; + } + if (this.responseInterceptors == null) { + this.responseInterceptors = new ArrayList(); + } + this.responseInterceptors.add(itcp); + } + + /** + * Same as {@link #addResponseInterceptor(HttpResponseInterceptor) addResponseInterceptor}. + * + * @param interceptor the interceptor to add + */ + public final + void addInterceptor(final HttpResponseInterceptor interceptor) { + addResponseInterceptor(interceptor); + } + + public final void addInterceptor(final HttpResponseInterceptor interceptor, + int index) { + addResponseInterceptor(interceptor, index); + } + + + + // non-Javadoc, see interface HttpResponseInterceptorList + public int getResponseInterceptorCount() { + return (this.responseInterceptors == null) ? + 0 : this.responseInterceptors.size(); + } + + + // non-Javadoc, see interface HttpResponseInterceptorList + public HttpResponseInterceptor getResponseInterceptor(int index) { + + if ((this.responseInterceptors == null) || + (index < 0) || (index >= this.responseInterceptors.size())) + return null; + + return (HttpResponseInterceptor) this.responseInterceptors.get(index); + } + + + // non-Javadoc, see interface HttpResponseInterceptorList + public void clearResponseInterceptors() { + this.responseInterceptors = null; + } + + + /** + * Sets the interceptor lists. + * First, both interceptor lists maintained by this processor + * will be cleared. + * Subsequently, + * elements of the argument list that are request interceptors will be + * added to the request interceptor list. + * Elements that are response interceptors will be + * added to the response interceptor list. + * Elements that are both request and response interceptor will be + * added to both lists. + * Elements that are neither request nor response interceptor + * will be ignored. + * + * @param list the list of request and response interceptors + * from which to initialize + */ + public void setInterceptors(final List list) { + if (list == null) { + throw new IllegalArgumentException("List must not be null."); + } + if (this.requestInterceptors != null) { + this.requestInterceptors.clear(); + } + if (this.responseInterceptors != null) { + this.responseInterceptors.clear(); + } + for (int i = 0; i < list.size(); i++) { + Object obj = list.get(i); + if (obj instanceof HttpRequestInterceptor) { + addInterceptor((HttpRequestInterceptor)obj); + } + if (obj instanceof HttpResponseInterceptor) { + addInterceptor((HttpResponseInterceptor)obj); + } + } + } + + /** + * Clears both interceptor lists maintained by this processor. + */ + public void clearInterceptors() { + clearRequestInterceptors(); + clearResponseInterceptors(); + } + + // non-Javadoc, see interface HttpRequestInterceptor (via HttpProcessor) + public void process( + final HttpRequest request, + final HttpContext context) + throws IOException, HttpException { + if (this.requestInterceptors != null) { + for (int i = 0; i < this.requestInterceptors.size(); i++) { + HttpRequestInterceptor interceptor = + (HttpRequestInterceptor) this.requestInterceptors.get(i); + interceptor.process(request, context); + } + } + } + + // non-Javadoc, see interface HttpResponseInterceptor (via HttpProcessor) + public void process( + final HttpResponse response, + final HttpContext context) + throws IOException, HttpException { + if (this.responseInterceptors != null) { + for (int i = 0; i < this.responseInterceptors.size(); i++) { + HttpResponseInterceptor interceptor = + (HttpResponseInterceptor) this.responseInterceptors.get(i); + interceptor.process(response, context); + } + } + } + + protected void copyInterceptors(final BasicHttpProcessor target) { + if (this.requestInterceptors != null) { + target.requestInterceptors = + new ArrayList(this.requestInterceptors); + } + if (this.responseInterceptors != null) { + target.responseInterceptors = + new ArrayList(this.responseInterceptors); + } + } + + /** + * Creates a copy of this instance + * + * @return new instance of the BasicHttpProcessor + */ + public BasicHttpProcessor copy() { + BasicHttpProcessor clone = new BasicHttpProcessor(); + copyInterceptors(clone); + return clone; + } + + public Object clone() throws CloneNotSupportedException { + BasicHttpProcessor clone = (BasicHttpProcessor) super.clone(); + copyInterceptors(clone); + return clone; + } + +} diff --git a/src/org/apache/http/protocol/DefaultedHttpContext.java b/src/org/apache/http/protocol/DefaultedHttpContext.java new file mode 100644 index 0000000..986f1a6 --- /dev/null +++ b/src/org/apache/http/protocol/DefaultedHttpContext.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/DefaultedHttpContext.java $ + * $Revision: 654882 $ + * $Date: 2008-05-09 09:58:59 -0700 (Fri, 09 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +/** + * {@link HttpContext} implementation that delegates resolution of an attribute + * to the given default {@link HttpContext} instance if the attribute is not + * present in the local one. The state of the local context can be mutated, + * whereas the default context is treated as read-only. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 654882 $ + */ +public final class DefaultedHttpContext implements HttpContext { + + private final HttpContext local; + private final HttpContext defaults; + + public DefaultedHttpContext(final HttpContext local, final HttpContext defaults) { + super(); + if (local == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + this.local = local; + this.defaults = defaults; + } + + public Object getAttribute(final String id) { + Object obj = this.local.getAttribute(id); + if (obj == null) { + return this.defaults.getAttribute(id); + } else { + return obj; + } + } + + public Object removeAttribute(final String id) { + return this.local.removeAttribute(id); + } + + public void setAttribute(final String id, final Object obj) { + this.local.setAttribute(id, obj); + } + + public HttpContext getDefaults() { + return this.defaults; + } + +} diff --git a/src/org/apache/http/protocol/ExecutionContext.java b/src/org/apache/http/protocol/ExecutionContext.java new file mode 100644 index 0000000..d14acb5 --- /dev/null +++ b/src/org/apache/http/protocol/ExecutionContext.java @@ -0,0 +1,52 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/ExecutionContext.java $ + * $Revision: 558154 $ + * $Date: 2007-07-20 14:29:02 -0700 (Fri, 20 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +/** + * {@link HttpContext Context} attribute names for protocol execution. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 558154 $ + * + * @since 4.0 + */ +public interface ExecutionContext { + + public static final String HTTP_CONNECTION = "http.connection"; + public static final String HTTP_REQUEST = "http.request"; + public static final String HTTP_RESPONSE = "http.response"; + public static final String HTTP_TARGET_HOST = "http.target_host"; + public static final String HTTP_PROXY_HOST = "http.proxy_host"; + public static final String HTTP_REQ_SENT = "http.request_sent"; + +} diff --git a/src/org/apache/http/protocol/HTTP.java b/src/org/apache/http/protocol/HTTP.java new file mode 100644 index 0000000..de76ca6 --- /dev/null +++ b/src/org/apache/http/protocol/HTTP.java @@ -0,0 +1,99 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HTTP.java $ + * $Revision: 555989 $ + * $Date: 2007-07-13 06:33:39 -0700 (Fri, 13 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +/** + * Constants and static helpers related to the HTTP protocol. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 555989 $ + * + * @since 4.0 + */ +public final class HTTP { + + public static final int CR = 13; // <US-ASCII CR, carriage return (13)> + public static final int LF = 10; // <US-ASCII LF, linefeed (10)> + public static final int SP = 32; // <US-ASCII SP, space (32)> + public static final int HT = 9; // <US-ASCII HT, horizontal-tab (9)> + + /** HTTP header definitions */ + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + public static final String CONTENT_LEN = "Content-Length"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final String EXPECT_DIRECTIVE = "Expect"; + public static final String CONN_DIRECTIVE = "Connection"; + public static final String TARGET_HOST = "Host"; + public static final String USER_AGENT = "User-Agent"; + public static final String DATE_HEADER = "Date"; + public static final String SERVER_HEADER = "Server"; + + /** HTTP expectations */ + public static final String EXPECT_CONTINUE = "100-Continue"; + + /** HTTP connection control */ + public static final String CONN_CLOSE = "Close"; + public static final String CONN_KEEP_ALIVE = "Keep-Alive"; + + /** Transfer encoding definitions */ + public static final String CHUNK_CODING = "chunked"; + public static final String IDENTITY_CODING = "identity"; + + /** Common charset definitions */ + public static final String UTF_8 = "UTF-8"; + public static final String UTF_16 = "UTF-16"; + public static final String US_ASCII = "US-ASCII"; + public static final String ASCII = "ASCII"; + public static final String ISO_8859_1 = "ISO-8859-1"; + + /** Default charsets */ + public static final String DEFAULT_CONTENT_CHARSET = ISO_8859_1; + public static final String DEFAULT_PROTOCOL_CHARSET = US_ASCII; + + /** Content type definitions */ + public final static String OCTET_STREAM_TYPE = "application/octet-stream"; + public final static String PLAIN_TEXT_TYPE = "text/plain"; + public final static String CHARSET_PARAM = "; charset="; + + /** Default content type */ + public final static String DEFAULT_CONTENT_TYPE = OCTET_STREAM_TYPE; + + public static boolean isWhitespace(char ch) { + return ch == SP || ch == HT || ch == CR || ch == LF; + } + + private HTTP() { + } + +} diff --git a/src/org/apache/http/protocol/HttpContext.java b/src/org/apache/http/protocol/HttpContext.java new file mode 100644 index 0000000..bcf36fd --- /dev/null +++ b/src/org/apache/http/protocol/HttpContext.java @@ -0,0 +1,58 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpContext.java $ + * $Revision: 558111 $ + * $Date: 2007-07-20 13:01:50 -0700 (Fri, 20 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +/** + * A context for executing a request. + * The context is used to tie together the request, the response, + * and optional application data. It is also used for internal data. + * Attribute names starting with the prefix "http." are + * {@link #RESERVED_PREFIX reserved} for internal data. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 558111 $ + * + * @since 4.0 + */ +public interface HttpContext { + + /** The prefix reserved for use by HTTP components. "http." */ + public static final String RESERVED_PREFIX = "http."; + + Object getAttribute(String id); + + void setAttribute(String id, Object obj); + + Object removeAttribute(String id); + +} diff --git a/src/org/apache/http/protocol/HttpDateGenerator.java b/src/org/apache/http/protocol/HttpDateGenerator.java new file mode 100644 index 0000000..bfb0863 --- /dev/null +++ b/src/org/apache/http/protocol/HttpDateGenerator.java @@ -0,0 +1,81 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpDateGenerator.java $ + * $Revision: 548066 $ + * $Date: 2007-06-17 09:51:55 -0700 (Sun, 17 Jun 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + + +/** + * Generates a date in the format required by the HTTP protocol. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 548066 $ + * + * @since 4.0 + */ +public class HttpDateGenerator { + + /** Date format pattern used to generate the header in RFC 1123 format. */ + public static final + String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; + + /** The time zone to use in the date header. */ + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + + private final DateFormat dateformat; + + private long dateAsLong = 0L; + private String dateAsText = null; + + public HttpDateGenerator() { + super(); + this.dateformat = new SimpleDateFormat(PATTERN_RFC1123, Locale.US); + this.dateformat.setTimeZone(GMT); + } + + public synchronized String getCurrentDate() { + long now = System.currentTimeMillis(); + if (now - this.dateAsLong > 1000) { + // Generate new date string + this.dateAsText = this.dateformat.format(new Date(now)); + this.dateAsLong = now; + } + return this.dateAsText; + } + +} diff --git a/src/org/apache/http/protocol/HttpExpectationVerifier.java b/src/org/apache/http/protocol/HttpExpectationVerifier.java new file mode 100644 index 0000000..9fa4316 --- /dev/null +++ b/src/org/apache/http/protocol/HttpExpectationVerifier.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpExpectationVerifier.java $ + * $Revision: 613298 $ + * $Date: 2008-01-18 14:09:22 -0800 (Fri, 18 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; + +/** + * Defines an interface to verify whether an incoming HTTP request meets + * the target server's expectations. + *<p> + * The Expect request-header field is used to indicate that particular + * server behaviors are required by the client. + *</p> + *<pre> + * Expect = "Expect" ":" 1#expectation + * + * expectation = "100-continue" | expectation-extension + * expectation-extension = token [ "=" ( token | quoted-string ) + * *expect-params ] + * expect-params = ";" token [ "=" ( token | quoted-string ) ] + *</pre> + *<p> + * A server that does not understand or is unable to comply with any of + * the expectation values in the Expect field of a request MUST respond + * with appropriate error status. The server MUST respond with a 417 + * (Expectation Failed) status if any of the expectations cannot be met + * or, if there are other problems with the request, some other 4xx + * status. + *</p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 613298 $ + * + * @since 4.0 + */ +public interface HttpExpectationVerifier { + + void verify(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException; + +} diff --git a/src/org/apache/http/protocol/HttpProcessor.java b/src/org/apache/http/protocol/HttpProcessor.java new file mode 100644 index 0000000..489220d --- /dev/null +++ b/src/org/apache/http/protocol/HttpProcessor.java @@ -0,0 +1,56 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpProcessor.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponseInterceptor; + +/** + * Performs interceptor processing of requests and responses. + * Specific interceptors typically interpret or update message headers, + * and they may wrap the message entity for example to implement a + * specific transport or content encoding. + * A <code>HttpProcessor</code> typically maintains a list of + * interceptors that will be applied to a request or response. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public interface HttpProcessor + extends HttpRequestInterceptor, HttpResponseInterceptor { + + // no additional methods +} diff --git a/src/org/apache/http/protocol/HttpRequestExecutor.java b/src/org/apache/http/protocol/HttpRequestExecutor.java new file mode 100644 index 0000000..71fa75a --- /dev/null +++ b/src/org/apache/http/protocol/HttpRequestExecutor.java @@ -0,0 +1,322 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpRequestExecutor.java $ + * $Revision: 576073 $ + * $Date: 2007-09-16 03:53:13 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; +import java.net.ProtocolException; + +import org.apache.http.HttpClientConnection; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.params.CoreProtocolPNames; + +/** + * Sends HTTP requests and receives the responses. + * Takes care of request preprocessing and response postprocessing + * by the respective interceptors. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 576073 $ + * + * @since 4.0 + */ +public class HttpRequestExecutor { + + /** + * Create a new request executor. + */ + public HttpRequestExecutor() { + super(); + } + + /** + * Decide whether a response comes with an entity. + * The implementation in this class is based on RFC 2616. + * Unknown methods and response codes are supposed to + * indicate responses with an entity. + * <br/> + * Derived executors can override this method to handle + * methods and response codes not specified in RFC 2616. + * + * @param request the request, to obtain the executed method + * @param response the response, to obtain the status code + */ + protected boolean canResponseHaveBody(final HttpRequest request, + final HttpResponse response) { + + if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) { + return false; + } + int status = response.getStatusLine().getStatusCode(); + return status >= HttpStatus.SC_OK + && status != HttpStatus.SC_NO_CONTENT + && status != HttpStatus.SC_NOT_MODIFIED + && status != HttpStatus.SC_RESET_CONTENT; + } + + /** + * Synchronously send a request and obtain the response. + * + * @param request the request to send. It will be preprocessed. + * @param conn the open connection over which to send + * + * @return the response to the request, postprocessed + * + * @throws HttpException in case of a protocol or processing problem + * @throws IOException in case of an I/O problem + */ + public HttpResponse execute( + final HttpRequest request, + final HttpClientConnection conn, + final HttpContext context) + throws IOException, HttpException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (conn == null) { + throw new IllegalArgumentException("Client connection may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + try { + HttpResponse response = doSendRequest(request, conn, context); + if (response == null) { + response = doReceiveResponse(request, conn, context); + } + return response; + } catch (IOException ex) { + conn.close(); + throw ex; + } catch (HttpException ex) { + conn.close(); + throw ex; + } catch (RuntimeException ex) { + conn.close(); + throw ex; + } + } + + /** + * Prepare a request for sending. + * + * @param request the request to prepare + * @param processor the processor to use + * @param context the context for sending the request + * + * @throws HttpException in case of a protocol or processing problem + * @throws IOException in case of an I/O problem + */ + public void preProcess( + final HttpRequest request, + final HttpProcessor processor, + final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (processor == null) { + throw new IllegalArgumentException("HTTP processor may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + processor.process(request, context); + } + + /** + * Send a request over a connection. + * This method also handles the expect-continue handshake if necessary. + * If it does not have to handle an expect-continue handshake, it will + * not use the connection for reading or anything else that depends on + * data coming in over the connection. + * + * @param request the request to send, already + * {@link #preProcess preprocessed} + * @param conn the connection over which to send the request, + * already established + * @param context the context for sending the request + * + * @return a terminal response received as part of an expect-continue + * handshake, or + * <code>null</code> if the expect-continue handshake is not used + * + * @throws HttpException in case of a protocol or processing problem + * @throws IOException in case of an I/O problem + */ + protected HttpResponse doSendRequest( + final HttpRequest request, + final HttpClientConnection conn, + final HttpContext context) + throws IOException, HttpException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (conn == null) { + throw new IllegalArgumentException("HTTP connection may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + HttpResponse response = null; + context.setAttribute(ExecutionContext.HTTP_REQ_SENT, Boolean.FALSE); + + conn.sendRequestHeader(request); + if (request instanceof HttpEntityEnclosingRequest) { + // Check for expect-continue handshake. We have to flush the + // headers and wait for an 100-continue response to handle it. + // If we get a different response, we must not send the entity. + boolean sendentity = true; + final ProtocolVersion ver = + request.getRequestLine().getProtocolVersion(); + if (((HttpEntityEnclosingRequest) request).expectContinue() && + !ver.lessEquals(HttpVersion.HTTP_1_0)) { + + conn.flush(); + // As suggested by RFC 2616 section 8.2.3, we don't wait for a + // 100-continue response forever. On timeout, send the entity. + int tms = request.getParams().getIntParameter( + CoreProtocolPNames.WAIT_FOR_CONTINUE, 2000); + + if (conn.isResponseAvailable(tms)) { + response = conn.receiveResponseHeader(); + if (canResponseHaveBody(request, response)) { + conn.receiveResponseEntity(response); + } + int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + if (status != HttpStatus.SC_CONTINUE) { + throw new ProtocolException( + "Unexpected response: " + response.getStatusLine()); + } + // discard 100-continue + response = null; + } else { + sendentity = false; + } + } + } + if (sendentity) { + conn.sendRequestEntity((HttpEntityEnclosingRequest) request); + } + } + conn.flush(); + context.setAttribute(ExecutionContext.HTTP_REQ_SENT, Boolean.TRUE); + return response; + } + + /** + * Wait for and receive a response. + * This method will automatically ignore intermediate responses + * with status code 1xx. + * + * @param request the request for which to obtain the response + * @param conn the connection over which the request was sent + * @param context the context for receiving the response + * + * @return the final response, not yet post-processed + * + * @throws HttpException in case of a protocol or processing problem + * @throws IOException in case of an I/O problem + */ + protected HttpResponse doReceiveResponse( + final HttpRequest request, + final HttpClientConnection conn, + final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (conn == null) { + throw new IllegalArgumentException("HTTP connection may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + HttpResponse response = null; + int statuscode = 0; + + while (response == null || statuscode < HttpStatus.SC_OK) { + + response = conn.receiveResponseHeader(); + if (canResponseHaveBody(request, response)) { + conn.receiveResponseEntity(response); + } + statuscode = response.getStatusLine().getStatusCode(); + + } // while intermediate response + + return response; + + } + + /** + * Finish a response. + * This includes post-processing of the response object. + * It does <i>not</i> read the response entity, if any. + * It does <i>not</i> allow for immediate re-use of the + * connection over which the response is coming in. + * + * @param response the response object to finish + * @param processor the processor to use + * @param context the context for post-processing the response + * + * @throws HttpException in case of a protocol or processing problem + * @throws IOException in case of an I/O problem + */ + public void postProcess( + final HttpResponse response, + final HttpProcessor processor, + final HttpContext context) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + if (processor == null) { + throw new IllegalArgumentException("HTTP processor may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + processor.process(response, context); + } + +} // class HttpRequestExecutor diff --git a/src/org/apache/http/protocol/HttpRequestHandler.java b/src/org/apache/http/protocol/HttpRequestHandler.java new file mode 100644 index 0000000..7494353 --- /dev/null +++ b/src/org/apache/http/protocol/HttpRequestHandler.java @@ -0,0 +1,53 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpRequestHandler.java $ + * $Revision: 613298 $ + * $Date: 2008-01-18 14:09:22 -0800 (Fri, 18 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 613298 $ + * + * @since 4.0 + */ +public interface HttpRequestHandler { + + void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException; + +} diff --git a/src/org/apache/http/protocol/HttpRequestHandlerRegistry.java b/src/org/apache/http/protocol/HttpRequestHandlerRegistry.java new file mode 100644 index 0000000..668d748 --- /dev/null +++ b/src/org/apache/http/protocol/HttpRequestHandlerRegistry.java @@ -0,0 +1,82 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpRequestHandlerRegistry.java $ + * $Revision: 630662 $ + * $Date: 2008-02-24 11:40:51 -0800 (Sun, 24 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.util.Map; + +/** + * Maintains a map of HTTP request handlers keyed by a request URI pattern. + * {@link HttpRequestHandler} instances can be looked up by request URI + * using the {@link HttpRequestHandlerResolver} interface.<br/> + * Patterns may have three formats: + * <ul> + * <li><code>*</code></li> + * <li><code>*<uri></code></li> + * <li><code><uri>*</code></li> + * </ul> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 630662 $ + */ +public class HttpRequestHandlerRegistry implements HttpRequestHandlerResolver { + + private final UriPatternMatcher matcher; + + public HttpRequestHandlerRegistry() { + matcher = new UriPatternMatcher(); + } + + public void register(final String pattern, final HttpRequestHandler handler) { + matcher.register(pattern, handler); + } + + public void unregister(final String pattern) { + matcher.unregister(pattern); + } + + public void setHandlers(final Map map) { + matcher.setHandlers(map); + } + + public HttpRequestHandler lookup(final String requestURI) { + return (HttpRequestHandler) matcher.lookup(requestURI); + } + + /** + * @deprecated + */ + protected boolean matchUriRequestPattern(final String pattern, final String requestUri) { + return matcher.matchUriRequestPattern(pattern, requestUri); + } + +} diff --git a/src/org/apache/http/protocol/HttpRequestHandlerResolver.java b/src/org/apache/http/protocol/HttpRequestHandlerResolver.java new file mode 100644 index 0000000..be92deb --- /dev/null +++ b/src/org/apache/http/protocol/HttpRequestHandlerResolver.java @@ -0,0 +1,46 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpRequestHandlerResolver.java $ + * $Revision: 613298 $ + * $Date: 2008-01-18 14:09:22 -0800 (Fri, 18 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +/** + * Interface to be implemented by objects that can resolve + * {@link HttpRequestHandler} instances by request URI. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 613298 $ + */ +public interface HttpRequestHandlerResolver { + + HttpRequestHandler lookup(String requestURI); + +} diff --git a/src/org/apache/http/protocol/HttpRequestInterceptorList.java b/src/org/apache/http/protocol/HttpRequestInterceptorList.java new file mode 100644 index 0000000..84ec761 --- /dev/null +++ b/src/org/apache/http/protocol/HttpRequestInterceptorList.java @@ -0,0 +1,120 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpRequestInterceptorList.java $ + * $Revision: 554903 $ + * $Date: 2007-07-10 03:54:17 -0700 (Tue, 10 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.util.List; + +import org.apache.http.HttpRequestInterceptor; + +/** + * Provides access to an ordered list of request interceptors. + * Lists are expected to be built upfront and used read-only afterwards + * for {@link HttpProcessor processing}. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 554903 $ + * + * @since 4.0 + */ +public interface HttpRequestInterceptorList { + + /** + * Appends a request interceptor to this list. + * + * @param itcp the request interceptor to add + */ + void addRequestInterceptor(HttpRequestInterceptor itcp) + ; + + + /** + * Inserts a request interceptor at the specified index. + * + * @param itcp the request interceptor to add + * @param index the index to insert the interceptor at + */ + void addRequestInterceptor(HttpRequestInterceptor itcp, int index); + + + /** + * Obtains the current size of this list. + * + * @return the number of request interceptors in this list + */ + int getRequestInterceptorCount() + ; + + + /** + * Obtains a request interceptor from this list. + * + * @param index the index of the interceptor to obtain, + * 0 for first + * + * @return the interceptor at the given index, or + * <code>null</code> if the index is out of range + */ + HttpRequestInterceptor getRequestInterceptor(int index) + ; + + + /** + * Removes all request interceptors from this list. + */ + void clearRequestInterceptors() + ; + + + /** + * Removes all request interceptor of the specified class + * + * @param clazz the class of the instances to be removed. + */ + void removeRequestInterceptorByClass(Class clazz); + + + /** + * Sets the request interceptors in this list. + * This list will be cleared and re-initialized to contain + * all request interceptors from the argument list. + * If the argument list includes elements that are not request + * interceptors, the behavior is implementation dependent. + * + * @param itcps the list of request interceptors + */ + void setInterceptors(List itcps) + ; + + +} // interface HttpRequestInterceptorList + diff --git a/src/org/apache/http/protocol/HttpResponseInterceptorList.java b/src/org/apache/http/protocol/HttpResponseInterceptorList.java new file mode 100644 index 0000000..8b5811b --- /dev/null +++ b/src/org/apache/http/protocol/HttpResponseInterceptorList.java @@ -0,0 +1,121 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpResponseInterceptorList.java $ + * $Revision: 554903 $ + * $Date: 2007-07-10 03:54:17 -0700 (Tue, 10 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + + +import java.util.List; + +import org.apache.http.HttpResponseInterceptor; + + +/** + * Provides access to an ordered list of response interceptors. + * Lists are expected to be built upfront and used read-only afterwards + * for {@link HttpProcessor processing}. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 554903 $ + * + * @since 4.0 + */ +public interface HttpResponseInterceptorList { + + /** + * Appends a response interceptor to this list. + * + * @param itcp the response interceptor to add + */ + void addResponseInterceptor(HttpResponseInterceptor itcp) + ; + + /** + * Inserts a response interceptor at the specified index. + * + * @param itcp the response interceptor to add + * @param index the index to insert the interceptor at + */ + void addResponseInterceptor(HttpResponseInterceptor itcp, int index); + + + /** + * Obtains the current size of this list. + * + * @return the number of response interceptors in this list + */ + int getResponseInterceptorCount() + ; + + + /** + * Obtains a response interceptor from this list. + * + * @param index the index of the interceptor to obtain, + * 0 for first + * + * @return the interceptor at the given index, or + * <code>null</code> if the index is out of range + */ + HttpResponseInterceptor getResponseInterceptor(int index) + ; + + + /** + * Removes all response interceptors from this list. + */ + void clearResponseInterceptors() + ; + + + /** + * Removes all response interceptor of the specified class + * + * @param clazz the class of the instances to be removed. + */ + void removeResponseInterceptorByClass(Class clazz); + + + /** + * Sets the response interceptors in this list. + * This list will be cleared and re-initialized to contain + * all response interceptors from the argument list. + * If the argument list includes elements that are not response + * interceptors, the behavior is implementation dependent. + * + * @param itcps the list of response interceptors + */ + void setInterceptors(List itcps) + ; + + +} // interface HttpResponseInterceptorList + diff --git a/src/org/apache/http/protocol/HttpService.java b/src/org/apache/http/protocol/HttpService.java new file mode 100644 index 0000000..991e931 --- /dev/null +++ b/src/org/apache/http/protocol/HttpService.java @@ -0,0 +1,249 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpService.java $ + * $Revision: 610763 $ + * $Date: 2008-01-10 04:01:13 -0800 (Thu, 10 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.HttpServerConnection; +import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; +import org.apache.http.MethodNotSupportedException; +import org.apache.http.ProtocolException; +import org.apache.http.ProtocolVersion; +import org.apache.http.UnsupportedHttpVersionException; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.params.HttpParams; +import org.apache.http.params.DefaultedHttpParams; +import org.apache.http.util.EncodingUtils; + +/** + * Minimalistic server-side implementation of an HTTP processor. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 610763 $ + */ +public class HttpService { + + private HttpParams params = null; + private HttpProcessor processor = null; + private HttpRequestHandlerResolver handlerResolver = null; + private ConnectionReuseStrategy connStrategy = null; + private HttpResponseFactory responseFactory = null; + private HttpExpectationVerifier expectationVerifier = null; + + /** + * Create a new HTTP service. + * + * @param proc the processor to use on requests and responses + * @param connStrategy the connection reuse strategy + * @param responseFactory the response factory + */ + public HttpService( + final HttpProcessor proc, + final ConnectionReuseStrategy connStrategy, + final HttpResponseFactory responseFactory) { + super(); + setHttpProcessor(proc); + setConnReuseStrategy(connStrategy); + setResponseFactory(responseFactory); + } + + public void setHttpProcessor(final HttpProcessor processor) { + if (processor == null) { + throw new IllegalArgumentException("HTTP processor may not be null."); + } + this.processor = processor; + } + + public void setConnReuseStrategy(final ConnectionReuseStrategy connStrategy) { + if (connStrategy == null) { + throw new IllegalArgumentException("Connection reuse strategy may not be null"); + } + this.connStrategy = connStrategy; + } + + public void setResponseFactory(final HttpResponseFactory responseFactory) { + if (responseFactory == null) { + throw new IllegalArgumentException("Response factory may not be null"); + } + this.responseFactory = responseFactory; + } + + public void setHandlerResolver(final HttpRequestHandlerResolver handlerResolver) { + this.handlerResolver = handlerResolver; + } + + public void setExpectationVerifier(final HttpExpectationVerifier expectationVerifier) { + this.expectationVerifier = expectationVerifier; + } + + public HttpParams getParams() { + return this.params; + } + + public void setParams(final HttpParams params) { + this.params = params; + } + + public void handleRequest( + final HttpServerConnection conn, + final HttpContext context) throws IOException, HttpException { + + context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn); + + HttpResponse response = null; + + try { + + HttpRequest request = conn.receiveRequestHeader(); + request.setParams( + new DefaultedHttpParams(request.getParams(), this.params)); + + ProtocolVersion ver = + request.getRequestLine().getProtocolVersion(); + if (!ver.lessEquals(HttpVersion.HTTP_1_1)) { + // Downgrade protocol version if greater than HTTP/1.1 + ver = HttpVersion.HTTP_1_1; + } + + if (request instanceof HttpEntityEnclosingRequest) { + + if (((HttpEntityEnclosingRequest) request).expectContinue()) { + response = this.responseFactory.newHttpResponse(ver, + HttpStatus.SC_CONTINUE, context); + response.setParams( + new DefaultedHttpParams(response.getParams(), this.params)); + + if (this.expectationVerifier != null) { + try { + this.expectationVerifier.verify(request, response, context); + } catch (HttpException ex) { + response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0, + HttpStatus.SC_INTERNAL_SERVER_ERROR, context); + response.setParams( + new DefaultedHttpParams(response.getParams(), this.params)); + handleException(ex, response); + } + } + if (response.getStatusLine().getStatusCode() < 200) { + // Send 1xx response indicating the server expections + // have been met + conn.sendResponseHeader(response); + conn.flush(); + response = null; + conn.receiveRequestEntity((HttpEntityEnclosingRequest) request); + } + } else { + conn.receiveRequestEntity((HttpEntityEnclosingRequest) request); + } + } + + if (response == null) { + response = this.responseFactory.newHttpResponse(ver, HttpStatus.SC_OK, context); + response.setParams( + new DefaultedHttpParams(response.getParams(), this.params)); + + context.setAttribute(ExecutionContext.HTTP_REQUEST, request); + context.setAttribute(ExecutionContext.HTTP_RESPONSE, response); + + this.processor.process(request, context); + doService(request, response, context); + } + + // Make sure the request content is fully consumed + if (request instanceof HttpEntityEnclosingRequest) { + HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity(); + if (entity != null) { + entity.consumeContent(); + } + } + + } catch (HttpException ex) { + response = this.responseFactory.newHttpResponse + (HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, + context); + response.setParams( + new DefaultedHttpParams(response.getParams(), this.params)); + handleException(ex, response); + } + + this.processor.process(response, context); + conn.sendResponseHeader(response); + conn.sendResponseEntity(response); + conn.flush(); + + if (!this.connStrategy.keepAlive(response, context)) { + conn.close(); + } + } + + protected void handleException(final HttpException ex, final HttpResponse response) { + if (ex instanceof MethodNotSupportedException) { + response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED); + } else if (ex instanceof UnsupportedHttpVersionException) { + response.setStatusCode(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED); + } else if (ex instanceof ProtocolException) { + response.setStatusCode(HttpStatus.SC_BAD_REQUEST); + } else { + response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + } + byte[] msg = EncodingUtils.getAsciiBytes(ex.getMessage()); + ByteArrayEntity entity = new ByteArrayEntity(msg); + entity.setContentType("text/plain; charset=US-ASCII"); + response.setEntity(entity); + } + + protected void doService( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws HttpException, IOException { + HttpRequestHandler handler = null; + if (this.handlerResolver != null) { + String requestURI = request.getRequestLine().getUri(); + handler = this.handlerResolver.lookup(requestURI); + } + if (handler != null) { + handler.handle(request, response, context); + } else { + response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED); + } + } + +} diff --git a/src/org/apache/http/protocol/RequestConnControl.java b/src/org/apache/http/protocol/RequestConnControl.java new file mode 100644 index 0000000..0a7088c --- /dev/null +++ b/src/org/apache/http/protocol/RequestConnControl.java @@ -0,0 +1,67 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/RequestConnControl.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; + +/** + * A request interceptor that suggests connection keep-alive to the server. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public class RequestConnControl implements HttpRequestInterceptor { + + public RequestConnControl() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (!request.containsHeader(HTTP.CONN_DIRECTIVE)) { + // Default policy is to keep connection alive + // whenever possible + request.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_KEEP_ALIVE); + } + } + +} diff --git a/src/org/apache/http/protocol/RequestContent.java b/src/org/apache/http/protocol/RequestContent.java new file mode 100644 index 0000000..745f604 --- /dev/null +++ b/src/org/apache/http/protocol/RequestContent.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/RequestContent.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.ProtocolException; + +/** + * A request interceptor that decides about the transport encoding. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public class RequestContent implements HttpRequestInterceptor { + + public RequestContent() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (request instanceof HttpEntityEnclosingRequest) { + if (request.containsHeader(HTTP.TRANSFER_ENCODING)) { + throw new ProtocolException("Transfer-encoding header already present"); + } + if (request.containsHeader(HTTP.CONTENT_LEN)) { + throw new ProtocolException("Content-Length header already present"); + } + ProtocolVersion ver = request.getRequestLine().getProtocolVersion(); + HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity(); + if (entity == null) { + request.addHeader(HTTP.CONTENT_LEN, "0"); + return; + } + // Must specify a transfer encoding or a content length + if (entity.isChunked() || entity.getContentLength() < 0) { + if (ver.lessEquals(HttpVersion.HTTP_1_0)) { + throw new ProtocolException( + "Chunked transfer encoding not allowed for " + ver); + } + request.addHeader(HTTP.TRANSFER_ENCODING, HTTP.CHUNK_CODING); + } else { + request.addHeader(HTTP.CONTENT_LEN, Long.toString(entity.getContentLength())); + } + // Specify a content type if known + if (entity.getContentType() != null && !request.containsHeader( + HTTP.CONTENT_TYPE )) { + request.addHeader(entity.getContentType()); + } + // Specify a content encoding if known + if (entity.getContentEncoding() != null && !request.containsHeader( + HTTP.CONTENT_ENCODING)) { + request.addHeader(entity.getContentEncoding()); + } + } + } + +} diff --git a/src/org/apache/http/protocol/RequestDate.java b/src/org/apache/http/protocol/RequestDate.java new file mode 100644 index 0000000..6462906 --- /dev/null +++ b/src/org/apache/http/protocol/RequestDate.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/RequestDate.java $ + * $Revision: 555989 $ + * $Date: 2007-07-13 06:33:39 -0700 (Fri, 13 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpRequestInterceptor; + +/** + * A request interceptor that adds a Date header. + * For use on the client side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 555989 $ + * + * @since 4.0 + */ +public class RequestDate implements HttpRequestInterceptor { + + private static final HttpDateGenerator DATE_GENERATOR = new HttpDateGenerator(); + + public RequestDate() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException + ("HTTP request may not be null."); + } + if ((request instanceof HttpEntityEnclosingRequest) && + !request.containsHeader(HTTP.DATE_HEADER)) { + String httpdate = DATE_GENERATOR.getCurrentDate(); + request.setHeader(HTTP.DATE_HEADER, httpdate); + } + } + +} diff --git a/src/org/apache/http/protocol/RequestExpectContinue.java b/src/org/apache/http/protocol/RequestExpectContinue.java new file mode 100644 index 0000000..0799849 --- /dev/null +++ b/src/org/apache/http/protocol/RequestExpectContinue.java @@ -0,0 +1,78 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/RequestExpectContinue.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.params.HttpProtocolParams; + +/** + * A request interceptor that enables the expect-continue handshake. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public class RequestExpectContinue implements HttpRequestInterceptor { + + public RequestExpectContinue() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (request instanceof HttpEntityEnclosingRequest) { + HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity(); + // Do not send the expect header if request body is known to be empty + if (entity != null && entity.getContentLength() != 0) { + ProtocolVersion ver = request.getRequestLine().getProtocolVersion(); + if (HttpProtocolParams.useExpectContinue(request.getParams()) + && !ver.lessEquals(HttpVersion.HTTP_1_0)) { + request.addHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE); + } + } + } + } + +} diff --git a/src/org/apache/http/protocol/RequestTargetHost.java b/src/org/apache/http/protocol/RequestTargetHost.java new file mode 100644 index 0000000..9349a8a --- /dev/null +++ b/src/org/apache/http/protocol/RequestTargetHost.java @@ -0,0 +1,98 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/RequestTargetHost.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; +import java.net.InetAddress; + +import org.apache.http.HttpConnection; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpInetConnection; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.ProtocolException; + +/** + * A request interceptor that sets the Host header for HTTP/1.1 requests. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public class RequestTargetHost implements HttpRequestInterceptor { + + public RequestTargetHost() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + if (!request.containsHeader(HTTP.TARGET_HOST)) { + HttpHost targethost = (HttpHost) context + .getAttribute(ExecutionContext.HTTP_TARGET_HOST); + if (targethost == null) { + HttpConnection conn = (HttpConnection) context + .getAttribute(ExecutionContext.HTTP_CONNECTION); + if (conn instanceof HttpInetConnection) { + // Populate the context with a default HTTP host based on the + // inet address of the target host + InetAddress address = ((HttpInetConnection) conn).getRemoteAddress(); + int port = ((HttpInetConnection) conn).getRemotePort(); + if (address != null) { + targethost = new HttpHost(address.getHostName(), port); + } + } + if (targethost == null) { + ProtocolVersion ver = request.getRequestLine().getProtocolVersion(); + if (ver.lessEquals(HttpVersion.HTTP_1_0)) { + return; + } else { + throw new ProtocolException("Target host missing"); + } + } + } + request.addHeader(HTTP.TARGET_HOST, targethost.toHostString()); + } + } + +} diff --git a/src/org/apache/http/protocol/RequestUserAgent.java b/src/org/apache/http/protocol/RequestUserAgent.java new file mode 100644 index 0000000..5a3145f --- /dev/null +++ b/src/org/apache/http/protocol/RequestUserAgent.java @@ -0,0 +1,69 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/RequestUserAgent.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.params.HttpProtocolParams; + +/** + * A request interceptor that adds a User-Agent header. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public class RequestUserAgent implements HttpRequestInterceptor { + + public RequestUserAgent() { + super(); + } + + public void process(final HttpRequest request, final HttpContext context) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (!request.containsHeader(HTTP.USER_AGENT)) { + String useragent = HttpProtocolParams.getUserAgent(request.getParams()); + if (useragent != null) { + request.addHeader(HTTP.USER_AGENT, useragent); + } + } + } + +} diff --git a/src/org/apache/http/protocol/ResponseConnControl.java b/src/org/apache/http/protocol/ResponseConnControl.java new file mode 100644 index 0000000..2e535fe --- /dev/null +++ b/src/org/apache/http/protocol/ResponseConnControl.java @@ -0,0 +1,104 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/ResponseConnControl.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; + +/** + * A response interceptor that suggests connection keep-alive to the client. + * For use on the server side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public class ResponseConnControl implements HttpResponseInterceptor { + + public ResponseConnControl() { + super(); + } + + public void process(final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + // Always drop connection after certain type of responses + int status = response.getStatusLine().getStatusCode(); + if (status == HttpStatus.SC_BAD_REQUEST || + status == HttpStatus.SC_REQUEST_TIMEOUT || + status == HttpStatus.SC_LENGTH_REQUIRED || + status == HttpStatus.SC_REQUEST_TOO_LONG || + status == HttpStatus.SC_REQUEST_URI_TOO_LONG || + status == HttpStatus.SC_SERVICE_UNAVAILABLE || + status == HttpStatus.SC_NOT_IMPLEMENTED) { + response.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE); + return; + } + // Always drop connection for HTTP/1.0 responses and below + // if the content body cannot be correctly delimited + HttpEntity entity = response.getEntity(); + if (entity != null) { + ProtocolVersion ver = response.getStatusLine().getProtocolVersion(); + if (entity.getContentLength() < 0 && + (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0))) { + response.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE); + return; + } + } + // Drop connection if requested by the client + HttpRequest request = (HttpRequest) + context.getAttribute(ExecutionContext.HTTP_REQUEST); + if (request != null) { + Header header = request.getFirstHeader(HTTP.CONN_DIRECTIVE); + if (header != null) { + response.setHeader(HTTP.CONN_DIRECTIVE, header.getValue()); + } + } + } + +} diff --git a/src/org/apache/http/protocol/ResponseContent.java b/src/org/apache/http/protocol/ResponseContent.java new file mode 100644 index 0000000..d1ac054 --- /dev/null +++ b/src/org/apache/http/protocol/ResponseContent.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/ResponseContent.java $ + * $Revision: 573864 $ + * $Date: 2007-09-08 08:53:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.ProtocolException; + +/** + * A response interceptor that sets up entity-related headers. + * For use on the server side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573864 $ + * + * @since 4.0 + */ +public class ResponseContent implements HttpResponseInterceptor { + + public ResponseContent() { + super(); + } + + public void process(final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (response.containsHeader(HTTP.TRANSFER_ENCODING)) { + throw new ProtocolException("Transfer-encoding header already present"); + } + if (response.containsHeader(HTTP.CONTENT_LEN)) { + throw new ProtocolException("Content-Length header already present"); + } + ProtocolVersion ver = response.getStatusLine().getProtocolVersion(); + HttpEntity entity = response.getEntity(); + if (entity != null) { + long len = entity.getContentLength(); + if (entity.isChunked() && !ver.lessEquals(HttpVersion.HTTP_1_0)) { + response.addHeader(HTTP.TRANSFER_ENCODING, HTTP.CHUNK_CODING); + } else if (len >= 0) { + response.addHeader(HTTP.CONTENT_LEN, Long.toString(entity.getContentLength())); + } + // Specify a content type if known + if (entity.getContentType() != null && !response.containsHeader( + HTTP.CONTENT_TYPE )) { + response.addHeader(entity.getContentType()); + } + // Specify a content encoding if known + if (entity.getContentEncoding() != null && !response.containsHeader( + HTTP.CONTENT_ENCODING)) { + response.addHeader(entity.getContentEncoding()); + } + } else { + int status = response.getStatusLine().getStatusCode(); + if (status != HttpStatus.SC_NO_CONTENT + && status != HttpStatus.SC_NOT_MODIFIED + && status != HttpStatus.SC_RESET_CONTENT) { + response.addHeader(HTTP.CONTENT_LEN, "0"); + } + } + } + +} diff --git a/src/org/apache/http/protocol/ResponseDate.java b/src/org/apache/http/protocol/ResponseDate.java new file mode 100644 index 0000000..431dc19 --- /dev/null +++ b/src/org/apache/http/protocol/ResponseDate.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/ResponseDate.java $ + * $Revision: 555989 $ + * $Date: 2007-07-13 06:33:39 -0700 (Fri, 13 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpStatus; + +/** + * A response interceptor that adds a Date header. + * For use on the server side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 555989 $ + * + * @since 4.0 + */ +public class ResponseDate implements HttpResponseInterceptor { + + private static final HttpDateGenerator DATE_GENERATOR = new HttpDateGenerator(); + + public ResponseDate() { + super(); + } + + public void process(final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException + ("HTTP response may not be null."); + } + int status = response.getStatusLine().getStatusCode(); + if ((status >= HttpStatus.SC_OK) && + !response.containsHeader(HTTP.DATE_HEADER)) { + String httpdate = DATE_GENERATOR.getCurrentDate(); + response.setHeader(HTTP.DATE_HEADER, httpdate); + } + } + +} diff --git a/src/org/apache/http/protocol/ResponseServer.java b/src/org/apache/http/protocol/ResponseServer.java new file mode 100644 index 0000000..44df593 --- /dev/null +++ b/src/org/apache/http/protocol/ResponseServer.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/ResponseServer.java $ + * $Revision: 576073 $ + * $Date: 2007-09-16 03:53:13 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.params.CoreProtocolPNames; + +/** + * A response interceptor that adds a Server header. + * For use on the server side. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 576073 $ + * + * @since 4.0 + */ +public class ResponseServer implements HttpResponseInterceptor { + + public ResponseServer() { + super(); + } + + public void process(final HttpResponse response, final HttpContext context) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + if (!response.containsHeader(HTTP.SERVER_HEADER)) { + String s = (String) response.getParams().getParameter( + CoreProtocolPNames.ORIGIN_SERVER); + if (s != null) { + response.addHeader(HTTP.SERVER_HEADER, s); + } + } + } + +} diff --git a/src/org/apache/http/protocol/SyncBasicHttpContext.java b/src/org/apache/http/protocol/SyncBasicHttpContext.java new file mode 100644 index 0000000..b1a408b --- /dev/null +++ b/src/org/apache/http/protocol/SyncBasicHttpContext.java @@ -0,0 +1,61 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/SyncBasicHttpContext.java $ + * $Revision: 613298 $ + * $Date: 2008-01-18 14:09:22 -0800 (Fri, 18 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +/** + * Thread-safe extension of the {@link BasicHttpContext}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 613298 $ + * + * @since 4.0 + */ +public class SyncBasicHttpContext extends BasicHttpContext { + + public SyncBasicHttpContext(final HttpContext parentContext) { + super(parentContext); + } + + public synchronized Object getAttribute(final String id) { + return super.getAttribute(id); + } + + public synchronized void setAttribute(final String id, final Object obj) { + super.setAttribute(id, obj); + } + + public synchronized Object removeAttribute(final String id) { + return super.removeAttribute(id); + } + +} diff --git a/src/org/apache/http/protocol/UriPatternMatcher.java b/src/org/apache/http/protocol/UriPatternMatcher.java new file mode 100644 index 0000000..2870d99 --- /dev/null +++ b/src/org/apache/http/protocol/UriPatternMatcher.java @@ -0,0 +1,127 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/UriPatternMatcher.java $ + * $Revision: 630662 $ + * $Date: 2008-02-24 11:40:51 -0800 (Sun, 24 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.protocol; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Maintains a map of objects keyed by a request URI pattern. + * Instances can be looked up by request URI.<br/> + * Patterns may have three formats: + * <ul> + * <li><code>*</code></li> + * <li><code>*<uri></code></li> + * <li><code><uri>*</code></li> + * </ul> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 630662 $ + */ +public class UriPatternMatcher { + + private final Map handlerMap; + + public UriPatternMatcher() { + super(); + this.handlerMap = new HashMap(); + } + + public void register(final String pattern, final Object handler) { + if (pattern == null) { + throw new IllegalArgumentException("URI request pattern may not be null"); + } + if (handler == null) { + throw new IllegalArgumentException("HTTP request handelr may not be null"); + } + this.handlerMap.put(pattern, handler); + } + + public void unregister(final String pattern) { + if (pattern == null) { + return; + } + this.handlerMap.remove(pattern); + } + + public void setHandlers(final Map map) { + if (map == null) { + throw new IllegalArgumentException("Map of handlers may not be null"); + } + this.handlerMap.clear(); + this.handlerMap.putAll(map); + } + + public Object lookup(String requestURI) { + if (requestURI == null) { + throw new IllegalArgumentException("Request URI may not be null"); + } + //Strip away the query part part if found + int index = requestURI.indexOf("?"); + if (index != -1) { + requestURI = requestURI.substring(0, index); + } + + // direct match? + Object handler = this.handlerMap.get(requestURI); + if (handler == null) { + // pattern match? + String bestMatch = null; + for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) { + String pattern = (String) it.next(); + if (matchUriRequestPattern(pattern, requestURI)) { + // we have a match. is it any better? + if (bestMatch == null + || (bestMatch.length() < pattern.length()) + || (bestMatch.length() == pattern.length() && pattern.endsWith("*"))) { + handler = this.handlerMap.get(pattern); + bestMatch = pattern; + } + } + } + } + return handler; + } + + protected boolean matchUriRequestPattern(final String pattern, final String requestUri) { + if (pattern.equals("*")) { + return true; + } else { + return + (pattern.endsWith("*") && requestUri.startsWith(pattern.substring(0, pattern.length() - 1))) || + (pattern.startsWith("*") && requestUri.endsWith(pattern.substring(1, pattern.length()))); + } + } + +} diff --git a/src/org/apache/http/protocol/package.html b/src/org/apache/http/protocol/package.html new file mode 100644 index 0000000..5056bcc --- /dev/null +++ b/src/org/apache/http/protocol/package.html @@ -0,0 +1,112 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/package.html $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +HTTP protocol execution framework. + +Apart from simply sending and receiving messages, there are a lot +of things to consider when communicating with HTTP. Many details +such as transport encodings or connection management are handled +by setting up or interpreting +{@link org.apache.http.Header headers} in the messages. +In order to relieve applications from the responsibility of +implementing these nitty-gritty details of the protocol, +HTTP components provides an execution framework that sets up +some of the headers before sending a message, and interprets +headers when a message has been received. +<br/> +An HTTP {@link org.apache.http.protocol.HttpProcessor processor} +typically keeps lists of so-called interceptors that will be executed +before a message is sent and after it has been received. +An application should initialize a processor, set up the lists +with the required and desired processors, and then communicate +through that processor. There are four kinds of interceptors, +depending on whether they act on +{@link org.apache.http.HttpRequestInterceptor requests} or +{@link org.apache.http.HttpResponseInterceptor responses}, +on the client or server side: + +<table border="1" cellpadding="5"> +<tr><th></th> + <th>Client</th> + <th>Server</th> +</tr> +<tr><th>Request</th> + <td>prepares headers before a request is sent</td> + <td>interprets headers when a request is received</td> +</tr> +<tr><th>Response</th> + <td>interprets headers when a response is received</td> + <td>prepares headers before a response is sent</td> +</tr> +</table> + +<p> +{@link org.apache.http.protocol.HttpRequestExecutor HttpRequestExecutor} +is a processor for the client side, +{@link org.apache.http.protocol.HttpService HttpService} +for the server side. +On the client side, a {@link org.apache.http.protocol.HttpContext context} +is used to tie together a request, the response to it, and other data +that might be associated with the request execution. It is passed to +the request executor whenever needed. +</p> + +<p> +<font size="+1"> +<i> +Information about required and recommended interceptors for the +client side will be provided elsewhere. For the time being, please +refer to the comments in the example applications or ask on +one of the mailing lists. +</i> +</font> +</p> + +<p> +<b>Note:</b> +If you want to develop a server-side application, we recommend that +you implement your application as a servlet running in a servlet engine +like <a href="http://tomcat.apache.org/">Tomcat</a> or full-blown +JSEE container like <a href="http://geronimo.apache.org/">Geronimo</a>. +If you prefer to implement a server-side application based on our +{@link org.apache.http.protocol.HttpService HttpService}, we'll +assume that you know what you're doing and that you don't need +help in figuring out which interceptors need to be configured. +</p> + + +</body> +</html> diff --git a/src/org/apache/http/svn.info b/src/org/apache/http/svn.info new file mode 100644 index 0000000..100deb5 --- /dev/null +++ b/src/org/apache/http/svn.info @@ -0,0 +1,8 @@ +Repository Root: http://svn.apache.org/repos/asf +Repository UUID: 13f79535-47bb-0310-9956-ffa450edef68 +Revision: 677354 +Node Kind: directory +Schedule: normal +Last Changed Author: olegk +Last Changed Rev: 677250 +Last Changed Date: 2008-07-16 04:45:47 -0700 (Wed, 16 Jul 2008) diff --git a/src/org/apache/http/util/ByteArrayBuffer.java b/src/org/apache/http/util/ByteArrayBuffer.java new file mode 100644 index 0000000..01d6577 --- /dev/null +++ b/src/org/apache/http/util/ByteArrayBuffer.java @@ -0,0 +1,162 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/ByteArrayBuffer.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.util; + +/** + * A resizable byte array. + * + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public final class ByteArrayBuffer { + + private byte[] buffer; + private int len; + + public ByteArrayBuffer(int capacity) { + super(); + if (capacity < 0) { + throw new IllegalArgumentException("Buffer capacity may not be negative"); + } + this.buffer = new byte[capacity]; + } + + private void expand(int newlen) { + byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)]; + System.arraycopy(this.buffer, 0, newbuffer, 0, this.len); + this.buffer = newbuffer; + } + + public void append(final byte[] b, int off, int len) { + if (b == null) { + return; + } + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) < 0) || ((off + len) > b.length)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + int newlen = this.len + len; + if (newlen > this.buffer.length) { + expand(newlen); + } + System.arraycopy(b, off, this.buffer, this.len, len); + this.len = newlen; + } + + public void append(int b) { + int newlen = this.len + 1; + if (newlen > this.buffer.length) { + expand(newlen); + } + this.buffer[this.len] = (byte)b; + this.len = newlen; + } + + public void append(final char[] b, int off, int len) { + if (b == null) { + return; + } + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) < 0) || ((off + len) > b.length)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + int oldlen = this.len; + int newlen = oldlen + len; + if (newlen > this.buffer.length) { + expand(newlen); + } + for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) { + this.buffer[i2] = (byte) b[i1]; + } + this.len = newlen; + } + + public void append(final CharArrayBuffer b, int off, int len) { + if (b == null) { + return; + } + append(b.buffer(), off, len); + } + + public void clear() { + this.len = 0; + } + + public byte[] toByteArray() { + byte[] b = new byte[this.len]; + if (this.len > 0) { + System.arraycopy(this.buffer, 0, b, 0, this.len); + } + return b; + } + + public int byteAt(int i) { + return this.buffer[i]; + } + + public int capacity() { + return this.buffer.length; + } + + public int length() { + return this.len; + } + + public byte[] buffer() { + return this.buffer; + } + + public void setLength(int len) { + if (len < 0 || len > this.buffer.length) { + throw new IndexOutOfBoundsException(); + } + this.len = len; + } + + public boolean isEmpty() { + return this.len == 0; + } + + public boolean isFull() { + return this.len == this.buffer.length; + } + +} diff --git a/src/org/apache/http/util/CharArrayBuffer.java b/src/org/apache/http/util/CharArrayBuffer.java new file mode 100644 index 0000000..b89f5d1 --- /dev/null +++ b/src/org/apache/http/util/CharArrayBuffer.java @@ -0,0 +1,264 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/CharArrayBuffer.java $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.util; + +import org.apache.http.protocol.HTTP; + +/** + * A resizable char array. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 496070 $ + * + * @since 4.0 + */ +public final class CharArrayBuffer { + + private char[] buffer; + private int len; + + public CharArrayBuffer(int capacity) { + super(); + if (capacity < 0) { + throw new IllegalArgumentException("Buffer capacity may not be negative"); + } + this.buffer = new char[capacity]; + } + + private void expand(int newlen) { + char newbuffer[] = new char[Math.max(this.buffer.length << 1, newlen)]; + System.arraycopy(this.buffer, 0, newbuffer, 0, this.len); + this.buffer = newbuffer; + } + + public void append(final char[] b, int off, int len) { + if (b == null) { + return; + } + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) < 0) || ((off + len) > b.length)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + int newlen = this.len + len; + if (newlen > this.buffer.length) { + expand(newlen); + } + System.arraycopy(b, off, this.buffer, this.len, len); + this.len = newlen; + } + + public void append(String str) { + if (str == null) { + str = "null"; + } + int strlen = str.length(); + int newlen = this.len + strlen; + if (newlen > this.buffer.length) { + expand(newlen); + } + str.getChars(0, strlen, this.buffer, this.len); + this.len = newlen; + } + + public void append(final CharArrayBuffer b, int off, int len) { + if (b == null) { + return; + } + append(b.buffer, off, len); + } + + public void append(final CharArrayBuffer b) { + if (b == null) { + return; + } + append(b.buffer,0, b.len); + } + + public void append(char ch) { + int newlen = this.len + 1; + if (newlen > this.buffer.length) { + expand(newlen); + } + this.buffer[this.len] = ch; + this.len = newlen; + } + + public void append(final byte[] b, int off, int len) { + if (b == null) { + return; + } + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) < 0) || ((off + len) > b.length)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + int oldlen = this.len; + int newlen = oldlen + len; + if (newlen > this.buffer.length) { + expand(newlen); + } + for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) { + int ch = b[i1]; + if (ch < 0) { + ch = 256 + ch; + } + this.buffer[i2] = (char) ch; + } + this.len = newlen; + } + + public void append(final ByteArrayBuffer b, int off, int len) { + if (b == null) { + return; + } + append(b.buffer(), off, len); + } + + public void append(final Object obj) { + append(String.valueOf(obj)); + } + + public void clear() { + this.len = 0; + } + + public char[] toCharArray() { + char[] b = new char[this.len]; + if (this.len > 0) { + System.arraycopy(this.buffer, 0, b, 0, this.len); + } + return b; + } + + public char charAt(int i) { + return this.buffer[i]; + } + + public char[] buffer() { + return this.buffer; + } + + public int capacity() { + return this.buffer.length; + } + + public int length() { + return this.len; + } + + public void ensureCapacity(int required) { + int available = this.buffer.length - this.len; + if (required > available) { + expand(this.len + required); + } + } + + public void setLength(int len) { + if (len < 0 || len > this.buffer.length) { + throw new IndexOutOfBoundsException(); + } + this.len = len; + } + + public boolean isEmpty() { + return this.len == 0; + } + + public boolean isFull() { + return this.len == this.buffer.length; + } + + public int indexOf(int ch, int beginIndex, int endIndex) { + if (beginIndex < 0) { + beginIndex = 0; + } + if (endIndex > this.len) { + endIndex = this.len; + } + if (beginIndex > endIndex) { + return -1; + } + for (int i = beginIndex; i < endIndex; i++) { + if (this.buffer[i] == ch) { + return i; + } + } + return -1; + } + + public int indexOf(int ch) { + return indexOf(ch, 0, this.len); + } + + public String substring(int beginIndex, int endIndex) { + if (beginIndex < 0) { + throw new IndexOutOfBoundsException(); + } + if (endIndex > this.len) { + throw new IndexOutOfBoundsException(); + } + if (beginIndex > endIndex) { + throw new IndexOutOfBoundsException(); + } + return new String(this.buffer, beginIndex, endIndex - beginIndex); + } + + public String substringTrimmed(int beginIndex, int endIndex) { + if (beginIndex < 0) { + throw new IndexOutOfBoundsException(); + } + if (endIndex > this.len) { + throw new IndexOutOfBoundsException(); + } + if (beginIndex > endIndex) { + throw new IndexOutOfBoundsException(); + } + while (beginIndex < endIndex && HTTP.isWhitespace(this.buffer[beginIndex])) { + beginIndex++; + } + while (endIndex > beginIndex && HTTP.isWhitespace(this.buffer[endIndex - 1])) { + endIndex--; + } + return new String(this.buffer, beginIndex, endIndex - beginIndex); + } + + public String toString() { + return new String(this.buffer, 0, this.len); + } + +} diff --git a/src/org/apache/http/util/EncodingUtils.java b/src/org/apache/http/util/EncodingUtils.java new file mode 100644 index 0000000..a1b3f44 --- /dev/null +++ b/src/org/apache/http/util/EncodingUtils.java @@ -0,0 +1,185 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/EncodingUtils.java $ + * $Revision: 503413 $ + * $Date: 2007-02-04 06:22:14 -0800 (Sun, 04 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.util; + +import java.io.UnsupportedEncodingException; + +import org.apache.http.protocol.HTTP; + +/** + * The home for utility methods that handle various encoding tasks. + * + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public final class EncodingUtils { + + /** + * Converts the byte array of HTTP content characters to a string. If + * the specified charset is not supported, default system encoding + * is used. + * + * @param data the byte array to be encoded + * @param offset the index of the first byte to encode + * @param length the number of bytes to encode + * @param charset the desired character encoding + * @return The result of the conversion. + */ + public static String getString( + final byte[] data, + int offset, + int length, + String charset + ) { + + if (data == null) { + throw new IllegalArgumentException("Parameter may not be null"); + } + + if (charset == null || charset.length() == 0) { + throw new IllegalArgumentException("charset may not be null or empty"); + } + + try { + return new String(data, offset, length, charset); + } catch (UnsupportedEncodingException e) { + return new String(data, offset, length); + } + } + + + /** + * Converts the byte array of HTTP content characters to a string. If + * the specified charset is not supported, default system encoding + * is used. + * + * @param data the byte array to be encoded + * @param charset the desired character encoding + * @return The result of the conversion. + */ + public static String getString(final byte[] data, final String charset) { + if (data == null) { + throw new IllegalArgumentException("Parameter may not be null"); + } + return getString(data, 0, data.length, charset); + } + + /** + * Converts the specified string to a byte array. If the charset is not supported the + * default system charset is used. + * + * @param data the string to be encoded + * @param charset the desired character encoding + * @return The resulting byte array. + */ + public static byte[] getBytes(final String data, final String charset) { + + if (data == null) { + throw new IllegalArgumentException("data may not be null"); + } + + if (charset == null || charset.length() == 0) { + throw new IllegalArgumentException("charset may not be null or empty"); + } + + try { + return data.getBytes(charset); + } catch (UnsupportedEncodingException e) { + return data.getBytes(); + } + } + + /** + * Converts the specified string to byte array of ASCII characters. + * + * @param data the string to be encoded + * @return The string as a byte array. + */ + public static byte[] getAsciiBytes(final String data) { + + if (data == null) { + throw new IllegalArgumentException("Parameter may not be null"); + } + + try { + return data.getBytes(HTTP.US_ASCII); + } catch (UnsupportedEncodingException e) { + throw new Error("HttpClient requires ASCII support"); + } + } + + /** + * Converts the byte array of ASCII characters to a string. This method is + * to be used when decoding content of HTTP elements (such as response + * headers) + * + * @param data the byte array to be encoded + * @param offset the index of the first byte to encode + * @param length the number of bytes to encode + * @return The string representation of the byte array + */ + public static String getAsciiString(final byte[] data, int offset, int length) { + + if (data == null) { + throw new IllegalArgumentException("Parameter may not be null"); + } + + try { + return new String(data, offset, length, HTTP.US_ASCII); + } catch (UnsupportedEncodingException e) { + throw new Error("HttpClient requires ASCII support"); + } + } + + /** + * Converts the byte array of ASCII characters to a string. This method is + * to be used when decoding content of HTTP elements (such as response + * headers) + * + * @param data the byte array to be encoded + * @return The string representation of the byte array + */ + public static String getAsciiString(final byte[] data) { + if (data == null) { + throw new IllegalArgumentException("Parameter may not be null"); + } + return getAsciiString(data, 0, data.length); + } + + /** + * This class should not be instantiated. + */ + private EncodingUtils() { + } + +} diff --git a/src/org/apache/http/util/EntityUtils.java b/src/org/apache/http/util/EntityUtils.java new file mode 100644 index 0000000..f9a7cf3 --- /dev/null +++ b/src/org/apache/http/util/EntityUtils.java @@ -0,0 +1,149 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/EntityUtils.java $ + * $Revision: 569637 $ + * $Date: 2007-08-25 00:36:59 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +import org.apache.http.HeaderElement; +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.protocol.HTTP; + +/** + * Static helpers for dealing with {@link HttpEntity entities}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 569637 $ + * + * @since 4.0 + */ +public final class EntityUtils { + + /** Disabled default constructor. */ + private EntityUtils() { + } + + public static byte[] toByteArray(final HttpEntity entity) throws IOException { + if (entity == null) { + throw new IllegalArgumentException("HTTP entity may not be null"); + } + InputStream instream = entity.getContent(); + if (instream == null) { + return new byte[] {}; + } + if (entity.getContentLength() > Integer.MAX_VALUE) { + throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); + } + int i = (int)entity.getContentLength(); + if (i < 0) { + i = 4096; + } + ByteArrayBuffer buffer = new ByteArrayBuffer(i); + try { + byte[] tmp = new byte[4096]; + int l; + while((l = instream.read(tmp)) != -1) { + buffer.append(tmp, 0, l); + } + } finally { + instream.close(); + } + return buffer.toByteArray(); + } + + public static String getContentCharSet(final HttpEntity entity) + throws ParseException { + + if (entity == null) { + throw new IllegalArgumentException("HTTP entity may not be null"); + } + String charset = null; + if (entity.getContentType() != null) { + HeaderElement values[] = entity.getContentType().getElements(); + if (values.length > 0) { + NameValuePair param = values[0].getParameterByName("charset"); + if (param != null) { + charset = param.getValue(); + } + } + } + return charset; + } + + public static String toString( + final HttpEntity entity, final String defaultCharset) throws IOException, ParseException { + if (entity == null) { + throw new IllegalArgumentException("HTTP entity may not be null"); + } + InputStream instream = entity.getContent(); + if (instream == null) { + return ""; + } + if (entity.getContentLength() > Integer.MAX_VALUE) { + throw new IllegalArgumentException("HTTP entity too large to be buffered in memory"); + } + int i = (int)entity.getContentLength(); + if (i < 0) { + i = 4096; + } + String charset = getContentCharSet(entity); + if (charset == null) { + charset = defaultCharset; + } + if (charset == null) { + charset = HTTP.DEFAULT_CONTENT_CHARSET; + } + Reader reader = new InputStreamReader(instream, charset); + CharArrayBuffer buffer = new CharArrayBuffer(i); + try { + char[] tmp = new char[1024]; + int l; + while((l = reader.read(tmp)) != -1) { + buffer.append(tmp, 0, l); + } + } finally { + reader.close(); + } + return buffer.toString(); + } + + public static String toString(final HttpEntity entity) + throws IOException, ParseException { + return toString(entity, null); + } + +} diff --git a/src/org/apache/http/util/ExceptionUtils.java b/src/org/apache/http/util/ExceptionUtils.java new file mode 100644 index 0000000..c7fdccd --- /dev/null +++ b/src/org/apache/http/util/ExceptionUtils.java @@ -0,0 +1,85 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/ExceptionUtils.java $ + * $Revision: 613003 $ + * $Date: 2008-01-17 15:00:42 -0800 (Thu, 17 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.util; + +import java.lang.reflect.Method; + +/** + * The home for utility methods that handle various exception-related tasks. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:laura@lwerner.org">Laura Werner</a> + * + * @since 4.0 + */ +public final class ExceptionUtils { + + /** A reference to Throwable's initCause method, or null if it's not there in this JVM */ + static private final Method INIT_CAUSE_METHOD = getInitCauseMethod(); + + /** + * Returns a <code>Method<code> allowing access to + * {@link Throwable#initCause(Throwable) initCause} method of {@link Throwable}, + * or <code>null</code> if the method + * does not exist. + * + * @return A <code>Method<code> for <code>Throwable.initCause</code>, or + * <code>null</code> if unavailable. + */ + static private Method getInitCauseMethod() { + try { + Class[] paramsClasses = new Class[] { Throwable.class }; + return Throwable.class.getMethod("initCause", paramsClasses); + } catch (NoSuchMethodException e) { + return null; + } + } + + /** + * If we're running on JDK 1.4 or later, initialize the cause for the given throwable. + * + * @param throwable The throwable. + * @param cause The cause of the throwable. + */ + public static void initCause(Throwable throwable, Throwable cause) { + if (INIT_CAUSE_METHOD != null) { + try { + INIT_CAUSE_METHOD.invoke(throwable, new Object[] { cause }); + } catch (Exception e) { + // Well, with no logging, the only option is to munch the exception + } + } + } + + private ExceptionUtils() { + } + +} diff --git a/src/org/apache/http/util/LangUtils.java b/src/org/apache/http/util/LangUtils.java new file mode 100644 index 0000000..a1dee8f --- /dev/null +++ b/src/org/apache/http/util/LangUtils.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/LangUtils.java $ + * $Revision: 503413 $ + * $Date: 2007-02-04 06:22:14 -0800 (Sun, 04 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.util; + +/** + * A set of utility methods to help produce consistent + * {@link Object#equals equals} and {@link Object#hashCode hashCode} methods. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public final class LangUtils { + + public static final int HASH_SEED = 17; + public static final int HASH_OFFSET = 37; + + /** Disabled default constructor. */ + private LangUtils() { + } + + public static int hashCode(final int seed, final int hashcode) { + return seed * HASH_OFFSET + hashcode; + } + + public static int hashCode(final int seed, final boolean b) { + return hashCode(seed, b ? 1 : 0); + } + + public static int hashCode(final int seed, final Object obj) { + return hashCode(seed, obj != null ? obj.hashCode() : 0); + } + + public static boolean equals(final Object obj1, final Object obj2) { + return obj1 == null ? obj2 == null : obj1.equals(obj2); + } + + public static boolean equals(final Object[] a1, final Object[] a2) { + if (a1 == null) { + if (a2 == null) { + return true; + } else { + return false; + } + } else { + if (a2 != null && a1.length == a2.length) { + for (int i = 0; i < a1.length; i++) { + if (!equals(a1[i], a2[i])) { + return false; + } + } + return true; + } else { + return false; + } + } + } + +} diff --git a/src/org/apache/http/util/VersionInfo.java b/src/org/apache/http/util/VersionInfo.java new file mode 100644 index 0000000..0c95594 --- /dev/null +++ b/src/org/apache/http/util/VersionInfo.java @@ -0,0 +1,317 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/VersionInfo.java $ + * $Revision: 554888 $ + * $Date: 2007-07-10 02:46:36 -0700 (Tue, 10 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; +import java.util.ArrayList; + + +/** + * Provides access to version information for HTTP components. + * Instances of this class provide version information for a single module + * or informal unit, as explained + * <a href="http://wiki.apache.org/jakarta-httpclient/HttpComponents">here</a>. + * Static methods are used to extract version information from property + * files that are automatically packaged with HTTP component release JARs. + * <br/> + * All available version information is provided in strings, where + * the string format is informal and subject to change without notice. + * Version information is provided for debugging output and interpretation + * by humans, not for automated processing in applications. + * + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * @author and others + */ +public class VersionInfo { + + /** A string constant for unavailable information. */ + public final static String UNAVAILABLE = "UNAVAILABLE"; + + /** The filename of the version information files. */ + public final static String VERSION_PROPERTY_FILE = "version.properties"; + + // the property names + public final static String PROPERTY_MODULE = "info.module"; + public final static String PROPERTY_RELEASE = "info.release"; + public final static String PROPERTY_TIMESTAMP = "info.timestamp"; + + + /** The package that contains the version information. */ + private final String infoPackage; + + /** The module from the version info. */ + private final String infoModule; + + /** The release from the version info. */ + private final String infoRelease; + + /** The timestamp from the version info. */ + private final String infoTimestamp; + + /** The classloader from which the version info was obtained. */ + private final String infoClassloader; + + + /** + * Instantiates version information. + * + * @param pckg the package + * @param module the module, or <code>null</code> + * @param release the release, or <code>null</code> + * @param time the build time, or <code>null</code> + * @param clsldr the class loader, or <code>null</code> + */ + protected VersionInfo(String pckg, String module, + String release, String time, String clsldr) { + if (pckg == null) { + throw new IllegalArgumentException + ("Package identifier must not be null."); + } + + infoPackage = pckg; + infoModule = (module != null) ? module : UNAVAILABLE; + infoRelease = (release != null) ? release : UNAVAILABLE; + infoTimestamp = (time != null) ? time : UNAVAILABLE; + infoClassloader = (clsldr != null) ? clsldr : UNAVAILABLE; + } + + + /** + * Obtains the package name. + * The package name identifies the module or informal unit. + * + * @return the package name, never <code>null</code> + */ + public final String getPackage() { + return infoPackage; + } + + /** + * Obtains the name of the versioned module or informal unit. + * This data is read from the version information for the package. + * + * @return the module name, never <code>null</code> + */ + public final String getModule() { + return infoModule; + } + + /** + * Obtains the release of the versioned module or informal unit. + * This data is read from the version information for the package. + * + * @return the release version, never <code>null</code> + */ + public final String getRelease() { + return infoRelease; + } + + /** + * Obtains the timestamp of the versioned module or informal unit. + * This data is read from the version information for the package. + * + * @return the timestamp, never <code>null</code> + */ + public final String getTimestamp() { + return infoTimestamp; + } + + /** + * Obtains the classloader used to read the version information. + * This is just the <code>toString</code> output of the classloader, + * since the version information should not keep a reference to + * the classloader itself. That could prevent garbage collection. + * + * @return the classloader description, never <code>null</code> + */ + public final String getClassloader() { + return infoClassloader; + } + + + /** + * Provides the version information in human-readable format. + * + * @return a string holding this version information + */ + public String toString() { + StringBuffer sb = new StringBuffer + (20 + infoPackage.length() + infoModule.length() + + infoRelease.length() + infoTimestamp.length() + + infoClassloader.length()); + + sb.append("VersionInfo(") + .append(infoPackage).append(':').append(infoModule); + + // If version info is missing, a single "UNAVAILABLE" for the module + // is sufficient. Everything else just clutters the output. + if (!UNAVAILABLE.equals(infoRelease)) + sb.append(':').append(infoRelease); + if (!UNAVAILABLE.equals(infoTimestamp)) + sb.append(':').append(infoTimestamp); + + sb.append(')'); + + if (!UNAVAILABLE.equals(infoClassloader)) + sb.append('@').append(infoClassloader); + + return sb.toString(); + } + + + /** + * Loads version information for a list of packages. + * + * @param pckgs the packages for which to load version info + * @param clsldr the classloader to load from, or + * <code>null</code> for the thread context classloader + * + * @return the version information for all packages found, + * never <code>null</code> + */ + public final static VersionInfo[] loadVersionInfo(String[] pckgs, + ClassLoader clsldr) { + if (pckgs == null) { + throw new IllegalArgumentException + ("Package identifier list must not be null."); + } + + ArrayList vil = new ArrayList(pckgs.length); + for (int i=0; i<pckgs.length; i++) { + VersionInfo vi = loadVersionInfo(pckgs[i], clsldr); + if (vi != null) + vil.add(vi); + } + + return (VersionInfo[]) vil.toArray(new VersionInfo[vil.size()]); + } + + + /** + * Loads version information for a package. + * + * @param pckg the package for which to load version information, + * for example "org.apache.http". + * The package name should NOT end with a dot. + * @param clsldr the classloader to load from, or + * <code>null</code> for the thread context classloader + * + * @return the version information for the argument package, or + * <code>null</code> if not available + */ + public final static VersionInfo loadVersionInfo(final String pckg, + ClassLoader clsldr) { + if (pckg == null) { + throw new IllegalArgumentException + ("Package identifier must not be null."); + } + + if (clsldr == null) + clsldr = Thread.currentThread().getContextClassLoader(); + + Properties vip = null; // version info properties, if available + try { + // org.apache.http becomes + // org/apache/http/version.properties + InputStream is = clsldr.getResourceAsStream + (pckg.replace('.', '/') + "/" + VERSION_PROPERTY_FILE); + if (is != null) { + try { + Properties props = new Properties(); + props.load(is); + vip = props; + } finally { + is.close(); + } + } + } catch (IOException ex) { + // shamelessly munch this exception + } + + VersionInfo result = null; + if (vip != null) + result = fromMap(pckg, vip, clsldr); + + return result; + } + + + /** + * Instantiates version information from properties. + * + * @param pckg the package for the version information + * @param info the map from string keys to string values, + * for example {@link java.util.Properties} + * @param clsldr the classloader, or <code>null</code> + * + * @return the version information + */ + protected final static VersionInfo fromMap(String pckg, Map info, + ClassLoader clsldr) { + if (pckg == null) { + throw new IllegalArgumentException + ("Package identifier must not be null."); + } + + String module = null; + String release = null; + String timestamp = null; + + if (info != null) { + module = (String) info.get(PROPERTY_MODULE); + if ((module != null) && (module.length() < 1)) + module = null; + + release = (String) info.get(PROPERTY_RELEASE); + if ((release != null) && ((release.length() < 1) || + (release.equals("${pom.version}")))) + release = null; + + timestamp = (String) info.get(PROPERTY_TIMESTAMP); + if ((timestamp != null) && + ((timestamp.length() < 1) || + (timestamp.equals("${mvn.timestamp}"))) + ) + timestamp = null; + } // if info + + String clsldrstr = null; + if (clsldr != null) + clsldrstr = clsldr.toString(); + + return new VersionInfo(pckg, module, release, timestamp, clsldrstr); + } + +} // class VersionInfo diff --git a/src/org/apache/http/util/package.html b/src/org/apache/http/util/package.html new file mode 100644 index 0000000..19d97b3 --- /dev/null +++ b/src/org/apache/http/util/package.html @@ -0,0 +1,45 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/package.html $ + * $Revision: 496070 $ + * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Mostly utility classes with static helper methods for various purposes. + +The helper classes for resizable +{@link org.apache.http.util.ByteArrayBuffer byte} and +{@link org.apache.http.util.CharArrayBuffer char} arrays +do not fall into this category. + +</body> +</html> |