/* * $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 * . * */ 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 #token sequences as * defined by RFC 2616, section 2. * It extends that definition somewhat beyond US-ASCII. * * @version $Revision: 602520 $ * * @deprecated Please use {@link java.net.URL#openConnection} instead. * Please visit this webpage * for further details. */ @Deprecated 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}. * null 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 null. * * @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}. *
* 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 #. */ 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 from 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 * #token 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 true if the character is a token separator, * false 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 true if the character is whitespace, * false 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 true if the character is a valid token start, * false 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 true if the character is an HTTP separator */ protected boolean isHttpSeparator(char ch) { return (HTTP_SEPARATORS.indexOf(ch) >= 0); } } // class BasicTokenIterator