jsdever 2003/02/10 19:23:05 Modified: httpclient/src/java/org/apache/commons/httpclient ChunkedInputStream.java HttpConnection.java HttpMethodBase.java httpclient/src/test/org/apache/commons/httpclient SimpleHttpConnection.java SimpleHttpMethod.java TestStreams.java Log: Multivalued headers. Bug: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=11218 Contributed by: Mike Becke changeLog: - HeaderGroup, a new class for storing headers. This class supports multiple headers with the same name and it remembers header order. I'm not a huge fan of the name, but it was the best I could come up with. - HeaderParser, a new class for parsing headers. Header parsing code was duplicated in HttpMethodBase and ChunkedInputStream and was placed here. - HttpMethod has 3 new methods. getRequestHeaderGroup(), getResponseHeaderGroup(), and getResponseFooterGroup(). This will break an existing extension of HttpMethod. - Some of the methods for accessing headers in HttpMethod have been deprecated. - Classes in the general codebase that use the newly deprecated methods have been fixed. Some of the test cases are still using deprecated methods. - SimpleHttpConnection and SimpleHttpMethod have been changed to work correctly with the new header storage method. Revision Changes Path 1.13 +18 -44 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java Index: ChunkedInputStream.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v retrieving revision 1.12 retrieving revision 1.13 diff -u -r1.12 -r1.13 --- ChunkedInputStream.java 28 Jan 2003 04:40:20 -0000 1.12 +++ ChunkedInputStream.java 11 Feb 2003 03:23:05 -0000 1.13 @@ -67,6 +67,8 @@ import java.io.IOException; import java.io.InputStream; +import org.apache.commons.httpclient.util.HeaderParser; + /** * <p>Transparently coalesces chunks of a HTTP stream that uses * Transfer-Encoding chunked.</p> @@ -84,6 +86,7 @@ * @author Martin Elwin * @author Eric Johnson * @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a> + * @author Michael Becke * * @since 2.0 * @@ -130,7 +133,7 @@ this.chunkSize = getChunkSizeFromInputStream(in); if (chunkSize == 0) { eof = true; - parseFooters(); + parseTrailerHeaders(); } this.pos = 0; } @@ -140,12 +143,14 @@ * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 * is detected.</p> * - * <p> Footers are read automcatically at the end of the stream and can be - * obtained with the getFooters() method.</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 + * + * @see HttpMethod#getResponseFooters() */ public int read() throws IOException { @@ -224,7 +229,7 @@ pos = 0; if (chunkSize == 0) { eof = true; - parseFooters(); + parseTrailerHeaders(); } } @@ -306,46 +311,15 @@ } /** - * Stores the footers into map of Headers + * Reads and stores the Trailer headers. * @throws IOException If an IO problem occurs */ - private void parseFooters() throws IOException { - String line = readLine(); - while ((line != null) && (!line.equals(""))) { - int colonPos = line.indexOf(':'); - if (colonPos != -1) { - String key = line.substring(0, colonPos).trim(); - String val = line.substring(colonPos + 1).trim(); - Header footer = new Header(key, val); - method.addResponseFooter(footer); - } - line = readLine(); - } - } - - /** - * Read the next line from {@link #in}. - * @return String The next line. - * @throws IOException If an IO problem occurs. - */ - private String readLine() throws IOException { - StringBuffer buf = new StringBuffer(); - while (true) { - int ch = in.read(); - if (ch < 0) { - if (buf.length() == 0) { - return null; - } else { - break; - } - } else if (ch == '\r') { - continue; - } else if (ch == '\n') { - break; - } - buf.append((char) ch); + private void parseTrailerHeaders() throws IOException { + Header[] footers = HeaderParser.parseHeaders(in); + + for (int i = 0; i < footers.length; i++) { + method.addResponseFooter(footers[i]); } - return (buf.toString()); } /** 1.43 +42 -22 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java Index: HttpConnection.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v retrieving revision 1.42 retrieving revision 1.43 diff -u -r1.42 -r1.43 --- HttpConnection.java 8 Feb 2003 19:22:49 -0000 1.42 +++ HttpConnection.java 11 Feb 2003 03:23:05 -0000 1.43 @@ -104,11 +104,48 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Jeff Dever</a> * @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a> * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> + * @author Michael Becke * * @version $Revision$ $Date$ */ public class HttpConnection { + /** + * Read up to <tt>"\r\n"</tt> from an (unchunked) input stream. + * If the stream ends before the line terminator is found, + * the last part of the string will still be returned. + * '\r' and '\n' are allowed to appear individually in the stream. + * + * @param inputStream the stream to read from + * + * @throws IOException if an I/O problem occurs + * @return a line from the stream + * + * @since 2.0beta1 + */ + public static String readLine(InputStream inputStream) throws IOException { + LOG.trace("enter HttpConnection.readLine()"); + + StringBuffer buf = new StringBuffer(); + int ch = inputStream.read(); + while (ch >= 0) { + if (ch == '\r') { + ch = inputStream.read(); + if (ch == '\n') { + break; + } else { + buf.append('\r'); + } + } + buf.append((char) ch); + ch = inputStream.read(); + } + if (WIRE_LOG.isDebugEnabled()) { + WIRE_LOG.debug("<< \"" + buf.toString() + (ch>0 ? "\" [\\r\\n]" : "")); + } + return (buf.toString()); + } + // ----------------------------------------------------------- Constructors /** @@ -857,24 +894,7 @@ LOG.trace("enter HttpConnection.readLine()"); assertOpen(); - StringBuffer buf = new StringBuffer(); - int ch = inputStream.read(); - while (ch >= 0) { - if (ch == '\r') { - ch = inputStream.read(); - if (ch == '\n') { - break; - } else { - buf.append('\r'); - } - } - buf.append((char) ch); - ch = inputStream.read(); - } - if (WIRE_LOG.isDebugEnabled()) { - WIRE_LOG.debug("<< \"" + buf.toString() + (ch>0 ? "\" [\\r\\n]" : "")); - } - return (buf.toString()); + return HttpConnection.readLine(inputStream); } /** 1.111 +95 -158 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java Index: HttpMethodBase.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.110 retrieving revision 1.111 diff -u -r1.110 -r1.111 --- HttpMethodBase.java 8 Feb 2003 19:22:49 -0000 1.110 +++ HttpMethodBase.java 11 Feb 2003 03:23:05 -0000 1.111 @@ -69,15 +69,13 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.cookie.CookieSpec; +import org.apache.commons.httpclient.util.HeaderParser; import org.apache.commons.httpclient.util.URIUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -165,16 +163,16 @@ // ----------------------------------------------------- Instance variables /** My request headers, if any. */ - private Map requestHeaders = new HashMap(); + private HeaderGroup requestHeaders = new HeaderGroup(); /** The Status-Line from the response. */ private StatusLine statusLine = null; /** My response headers, if any. */ - private Map responseHeaders = new HashMap(); + private HeaderGroup responseHeaders = new HeaderGroup(); - /** My response footers, if any. */ - private Map responseFooters = null; + /** My response trailer headers, if any. */ + private HeaderGroup responseTrailerHeaders = new HeaderGroup(); /** Realms that we tried to authenticate to */ private Set realms = null; @@ -416,12 +414,9 @@ } /** - * Add the specified request header. - * - * If a header of the same name already exists, the new value will be - * appended onto the the existing value list. - * A <i>header</i> value of <code>null</code> will be ignored. - * Note that header-name matching is case insensitive. + * Add the specified request header. A <i>header</i> value of + * <code>null</code> will be ignored. Note that header-name matching is case + * insensitive. * * @param header the header to add to the request */ @@ -429,9 +424,9 @@ LOG.trace("HttpMethodBase.addRequestHeader(Header)"); if (header == null) { - LOG.debug("null header value ignored"); + LOG.debug("null header value ignored"); } else { - addRequestHeader(header.getName(), header.getValue()); + getRequestHeaderGroup().addHeader(header); } } @@ -440,10 +435,7 @@ * @param footer The new footer to add. */ public void addResponseFooter(Header footer) { - if (responseFooters == null) { - responseFooters = new HashMap(); - } - responseFooters.put(footer.getName().toLowerCase(), footer); + getResponseTrailerHeaderGroup().addHeader(footer); } /** @@ -537,7 +529,15 @@ * @param header the header */ public void setRequestHeader(Header header) { - requestHeaders.put(header.getName().toLowerCase(), header); + + Header[] headers = getRequestHeaderGroup().getHeaders(header.getName()); + + for (int i = 0; i < headers.length; i++) { + getRequestHeaderGroup().removeHeader(headers[i]); + } + + getRequestHeaderGroup().addHeader(header); + } /** @@ -551,8 +551,11 @@ * @return the matching header */ public Header getRequestHeader(String headerName) { - return (headerName == null) - ? null : (Header) (requestHeaders.get(headerName.toLowerCase())); + if (headerName == null) { + return null; + } else { + return getRequestHeaderGroup().getCondensedHeader(headerName); + } } /** @@ -561,11 +564,44 @@ * @return an array of my request headers. */ public Header[] getRequestHeaders() { - return (Header[]) (requestHeaders.values().toArray( - new Header[requestHeaders.size()])); + return getRequestHeaderGroup().getAllHeaders(); + } + + /** + * Gets the HeaderGroup storing the request headers. + * + * @return a HeaderGroup + * + * @since 2.0beta1 + */ + protected HeaderGroup getRequestHeaderGroup() { + return requestHeaders; + } + + /** + * Gets the HeaderGroup storing the response trailer headers as per RFC + * 2616 section 3.6.1. + * + * @return a HeaderGroup + * + * @since 2.0beta1 + */ + protected HeaderGroup getResponseTrailerHeaderGroup() { + return responseTrailerHeaders; } /** + * Gets the HeaderGroup storing the response headers. + * + * @return a HeaderGroup + * + * @since 2.0beta1 + */ + protected HeaderGroup getResponseHeaderGroup() { + return responseHeaders; + } + + /** * Convenience method top provide access to the status code. * * @return the status code associated with the latest response. @@ -593,13 +629,12 @@ } /** - * Provide access to the response headers + * Gets the response headers in the order in which they were read. * * @return an array of my response headers. */ public Header[] getResponseHeaders() { - return (Header[]) (responseHeaders.values().toArray( - new Header[responseHeaders.size()])); + return getResponseHeaderGroup().getAllHeaders(); } /** @@ -612,10 +647,12 @@ * * @return the matching header */ - public Header getResponseHeader(String headerName) { - return (headerName == null) - ? null - : (Header) (responseHeaders.get(headerName.toLowerCase())); + public Header getResponseHeader(String headerName) { + if (headerName == null) { + return null; + } else { + return getResponseHeaderGroup().getCondensedHeader(headerName); + } } /** @@ -690,15 +727,11 @@ } /** - * Return an array of response footers. - * @return <tt>null</tt> if no footers are available + * Gets the response footers in the order in which they were read. + * @return an array of headers */ public Header[] getResponseFooters() { - if (responseFooters == null) { - return null; - } - return (Header[]) (responseFooters.values().toArray( - new Header[responseFooters.size()])); + return getResponseTrailerHeaderGroup().getAllHeaders(); } /** @@ -711,11 +744,11 @@ * @return the matching footer */ public Header getResponseFooter(String footerName) { - if (responseFooters == null) { + if (footerName == null) { return null; + } else { + return getResponseTrailerHeaderGroup().getCondensedHeader(footerName); } - return (footerName == null) ? null - : (Header) (responseFooters.get(footerName.toLowerCase())); } /** @@ -768,20 +801,7 @@ * @param headerValue the header's value */ public void addRequestHeader(String headerName, String headerValue) { - // "It must be possible to combine the multiple header fields into - // one "field-name: field-value" pair, without changing the - // semantics of the message, by appending each subsequent field-value - // to the first, each separated by a comma." - // - HTTP/1.0 (4.3) - Header header = getRequestHeader(headerName); - if (null == header) { - // header doesn't exist already, simply create with name and value - header = new Header(headerName, headerValue); - } else { - // header exists, add this value to the comma separated list - header.setValue(getNewHeaderValue(header, headerValue)); - } - setRequestHeader(header); + addRequestHeader(new Header(headerName, headerValue)); } /** @@ -1137,8 +1157,9 @@ followRedirects = false; doAuthentication = true; queryString = null; - requestHeaders.clear(); - responseHeaders.clear(); + getRequestHeaderGroup().clear(); + getResponseHeaderGroup().clear(); + getResponseTrailerHeaderGroup().clear(); statusLine = null; used = false; http11 = true; @@ -1174,7 +1195,12 @@ * @param headerName the header name */ public void removeRequestHeader(String headerName) { - requestHeaders.remove(headerName.toLowerCase()); + + Header[] headers = getRequestHeaderGroup().getHeaders(headerName); + for (int i = 0; i < headers.length; i++) { + getRequestHeaderGroup().removeHeader(headers[i]); + } + } // ---------------------------------------------------------------- Queries @@ -1835,55 +1861,9 @@ LOG.trace("enter HttpMethodBase.readResponseHeaders(HttpState," + "HttpConnection)"); - responseHeaders.clear(); - - String name = null; - String value = null; - for (; ;) { - String line = conn.readLine(); - if ((line == null) || (line.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 - boolean isFolded = false; - if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t')) { - // we have continuation folded header - // so append value - isFolded = true; - value = line.substring(1).trim(); - } else { - // Otherwise we should have normal HTTP header line - // Parse the header name and value - int colon = line.indexOf(":"); - if (colon < 0) { - throw new HttpException("Unable to parse header: " + line); - } - name = line.substring(0, colon).trim(); - value = line.substring(colon + 1).trim(); - } - Header header = getResponseHeader(name); - if (null == header) { - header = new Header(name, value); - } else { - String oldvalue = header.getValue(); - if (null != oldvalue) { - if (isFolded) { - // LWS becomes space plus extended value - header = new Header(name, oldvalue + " " + value); - } else { - // Append additional header value - header = new Header(name, oldvalue + ", " + value); - } - } else { - header = new Header(name, value); - } - } - setResponseHeader(header); - } + getResponseHeaderGroup().clear(); + Header[] headers = HeaderParser.parseHeaders(conn.getResponseInputStream()); + getResponseHeaderGroup().setHeaders(headers); } /** @@ -2048,9 +2028,10 @@ LOG.trace("enter HttpMethodBase.writeRequestHeaders(HttpState," + "HttpConnection)"); addRequestHeaders(state, conn); - Iterator it = requestHeaders.values().iterator(); - while (it.hasNext()) { - conn.print(((Header) it.next()).toExternalForm()); + + Header[] headers = getRequestHeaders(); + for (int i = 0; i < headers.length; i++) { + conn.print(headers[i].toExternalForm()); } } @@ -2136,50 +2117,6 @@ return false; } return true; - } - - - /** - * "It must be possible to combine the multiple header fields into one - * "field-name: field-value" pair, without changing the semantics of the - * message, by appending each subsequent field-value to the first, each - * separated by a comma." - * //TODO: This method is trying to make up for deficiencies in Header. - * - * @param existingHeader the current header - * @param value DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - private String getNewHeaderValue(Header existingHeader, String value) { - String existingValue = existingHeader.getValue(); - if (existingValue == null) { - existingValue = ""; - } - String newValue = value; - if (value == null) { - newValue = ""; - } - return existingValue + ", " + newValue; - } - - /** - * Sets the specified response header. - * Logs a warning if the header name is null. - * - * @param header the header to set. - * - * @since 2.0 - */ - private void setResponseHeader(Header header) { - if (header == null) { - return; - } - if (header.getName() == null) { - LOG.warn("Invalid header found"); - } else { - responseHeaders.put(header.getName().toLowerCase(), header); - } } /** 1.9 +38 -27 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpConnection.java Index: SimpleHttpConnection.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpConnection.java,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- SimpleHttpConnection.java 31 Jan 2003 23:23:17 -0000 1.8 +++ SimpleHttpConnection.java 11 Feb 2003 03:23:05 -0000 1.9 @@ -63,24 +63,24 @@ package org.apache.commons.httpclient; -import org.apache.commons.httpclient.protocol.Protocol; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.StringReader; +import java.io.OutputStreamWriter; import java.util.Vector; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + /** * For test-nohost testing purposes only. * * @author <a href="mailto:[EMAIL PROTECTED]">Jeff Dever</a> + * @author Michael Becke */ class SimpleHttpConnection extends HttpConnection { @@ -90,8 +90,9 @@ Vector headers = new Vector(); Vector bodies = new Vector(); - BufferedReader headerReader = null; - ByteArrayInputStream bodyInputStream = null; + + ByteArrayInputStream inputStream; + ByteArrayOutputStream bodyOutputStream = null; public void addResponse(String header) { @@ -122,26 +123,40 @@ } public void assertOpen() throws IllegalStateException { - if (bodyInputStream == null) { + if (inputStream == null) { throw new IllegalStateException(); } } public void assertNotOpen() throws IllegalStateException{ - if (bodyInputStream != null) { + if (inputStream != null) { throw new IllegalStateException(); } } public void open() throws IOException { - if (headerReader != null) return; + if (inputStream != null) return; try{ log.debug("hit: " + hits); - headerReader = new BufferedReader( - new StringReader((String)headers.elementAt(hits))); - bodyInputStream = new ByteArrayInputStream( - HttpConstants.getContentBytes((String)bodies.elementAt(hits))); + + // write the header to a byte array + ByteArrayOutputStream headerOutputStream = new ByteArrayOutputStream(); + OutputStreamWriter writer = new OutputStreamWriter( headerOutputStream ); + writer.write((String) headers.elementAt(hits)); + // terminate the headers + writer.write("\r\n"); + writer.close(); + + byte[] headerContent = headerOutputStream.toByteArray(); + byte[] bodyContent = HttpConstants.getContentBytes((String)bodies.elementAt(hits)); + + // combine the header and body content so they can be read from one steam + byte[] content = new byte[headerContent.length + bodyContent.length]; + System.arraycopy(headerContent, 0, content, 0, headerContent.length); + System.arraycopy(bodyContent, 0, content, headerContent.length, bodyContent.length); + + inputStream = new ByteArrayInputStream( content ); bodyOutputStream = new ByteArrayOutputStream(); hits++; } catch (ArrayIndexOutOfBoundsException aiofbe) { @@ -151,13 +166,9 @@ } public void close() { - if (headerReader != null) { - try { headerReader.close(); } catch(IOException e) {} - headerReader = null; - } - if (bodyInputStream != null) { - try { bodyInputStream.close(); } catch(IOException e) {} - bodyInputStream = null; + if (inputStream != null) { + try { inputStream.close(); } catch(IOException e) {} + inputStream = null; } if (bodyOutputStream != null) { try { bodyOutputStream.close(); } catch(IOException e) {} @@ -175,7 +186,7 @@ public String readLine() throws IOException, IllegalStateException { - String str = headerReader.readLine(); + String str = HttpConnection.readLine(inputStream); log.debug("read: " + str); return str; } @@ -187,7 +198,7 @@ public InputStream getResponseInputStream() { - return bodyInputStream; + return inputStream; } public OutputStream getRequestOutputStream() { 1.5 +42 -20 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpMethod.java Index: SimpleHttpMethod.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpMethod.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- SimpleHttpMethod.java 23 Jan 2003 22:48:25 -0000 1.4 +++ SimpleHttpMethod.java 11 Feb 2003 03:23:05 -0000 1.5 @@ -96,26 +96,48 @@ return "Simple"; } - public Header getResponseHeader(String name) { - try { - if(name.equalsIgnoreCase(header.getName())) { - return header; - } else { - return super.getResponseHeader(name); - } - } catch(NullPointerException e) { - return super.getResponseHeader(name); + /** + * Makes sure any respose header that exists has been added to the response + * header group. + */ + private void ensureResponseHeaderIsSet() { + if ( header != null ) { + super.getResponseHeaderGroup().addHeader(header); + header = null; } } + /** + * @see HttpMethod#execute(HttpState, HttpConnection) + */ + public int execute(HttpState state, HttpConnection connection) + throws HttpException, IOException { + return super.execute(state, connection); + } + + /** + * @see HttpMethod#getResponseHeader(String) + * @deprecated + */ + public Header getResponseHeader(String headerName) { + ensureResponseHeaderIsSet(); + return super.getResponseHeader(headerName); + } - public int execute(HttpState state, HttpConnection conn) - throws HttpException, IOException{ - return super.execute(state, conn); - } + /** + * @see HttpMethod#getResponseHeaderGroup() + */ + protected HeaderGroup getResponseHeaderGroup() { + ensureResponseHeaderIsSet(); + return super.getResponseHeaderGroup(); + } + + /** + * @see HttpMethod#getResponseHeaders() + */ + public Header[] getResponseHeaders() { + ensureResponseHeaderIsSet(); + return super.getResponseHeaders(); + } - public void addRequestHeaders(HttpState state, HttpConnection conn) - throws HttpException, IOException{ - super.addRequestHeaders(state, conn); - } } 1.9 +5 -3 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestStreams.java Index: TestStreams.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestStreams.java,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- TestStreams.java 23 Jan 2003 22:48:27 -0000 1.8 +++ TestStreams.java 11 Feb 2003 03:23:05 -0000 1.9 @@ -101,6 +101,8 @@ footer = method.getResponseFooter("footer2"); assertEquals(footer.getValue(), "fghij"); + // recycle the method so that it can be reused below + method.recycle(); //Test for when buffer is smaller than chunk size. in = new ChunkedInputStream(new ByteArrayInputStream(HttpConstants.getBytes(correctInput)), method);
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]