Author: rdonkin Date: Wed Mar 26 14:34:56 2008 New Revision: 641587 URL: http://svn.apache.org/viewvc?rev=641587&view=rev Log: Parse partial FETCH
Added: james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParserPartialFetchTest.java Modified: james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/FetchData.java james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/base/AbstractImapCommandParser.java james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java Modified: james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java URL: http://svn.apache.org/viewvc/james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java?rev=641587&r1=641586&r2=641587&view=diff ============================================================================== --- james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java (original) +++ james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/BodyFetchElement.java Wed Mar 26 14:34:56 2008 @@ -18,6 +18,7 @@ ****************************************************************/ package org.apache.james.api.imap.message; +import java.util.Arrays; import java.util.Collection; import org.apache.james.api.imap.ImapConstants; @@ -32,9 +33,9 @@ public static final int HEADER_NOT_FIELDS = 4; public static final int CONTENT = 5; - private static final BodyFetchElement rfc822 = new BodyFetchElement(ImapConstants.FETCH_RFC822, CONTENT, null, null); - private static final BodyFetchElement rfc822Header = new BodyFetchElement(ImapConstants.FETCH_RFC822_HEADER, HEADER, null, null); - private static final BodyFetchElement rfc822Text = new BodyFetchElement(ImapConstants.FETCH_RFC822_TEXT, TEXT, null, null); + private static final BodyFetchElement rfc822 = new BodyFetchElement(ImapConstants.FETCH_RFC822, CONTENT, null, null, null, null); + private static final BodyFetchElement rfc822Header = new BodyFetchElement(ImapConstants.FETCH_RFC822_HEADER, HEADER, null, null, null, null); + private static final BodyFetchElement rfc822Text = new BodyFetchElement(ImapConstants.FETCH_RFC822_TEXT, TEXT, null, null, null, null); public static final BodyFetchElement createRFC822() { return rfc822; @@ -48,19 +49,22 @@ return rfc822Text; } - + private final Long firstOctet; + private final Long numberOfOctets; private final String name; private final int sectionType; private final int[] path; private final Collection fieldNames; public BodyFetchElement( final String name, final int sectionType, - final int[] path, final Collection fieldNames) + final int[] path, final Collection fieldNames, Long firstOctet, Long numberOfOctets) { this.name = name; this.sectionType = sectionType; this.fieldNames = fieldNames; this.path = path; + this.firstOctet = firstOctet; + this.numberOfOctets = numberOfOctets; } public String getResponseName() { @@ -94,4 +98,71 @@ public final int getSectionType() { return sectionType; } + + /** + * Gets the first octet for a partial fetch. + * @return the firstOctet when this is a partial fetch + * or null + */ + public final Long getFirstOctet() { + return firstOctet; + } + + /** + * For a partial fetch, gets the number of octets to be returned. + * @return the lastOctet, + * or null + */ + public final Long getNumberOfOctets() { + return numberOfOctets; + } + + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((fieldNames == null) ? 0 : fieldNames.hashCode()); + result = PRIME * result + ((firstOctet == null) ? 0 : firstOctet.hashCode()); + result = PRIME * result + ((name == null) ? 0 : name.hashCode()); + result = PRIME * result + ((numberOfOctets == null) ? 0 : numberOfOctets.hashCode()); + result = PRIME * result + ((path == null) ? 0 : path.length); + result = PRIME * result + sectionType; + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final BodyFetchElement other = (BodyFetchElement) obj; + if (fieldNames == null) { + if (other.fieldNames != null) + return false; + } else if (!fieldNames.equals(other.fieldNames)) + return false; + if (firstOctet == null) { + if (other.firstOctet != null) + return false; + } else if (!firstOctet.equals(other.firstOctet)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (numberOfOctets == null) { + if (other.numberOfOctets != null) + return false; + } else if (!numberOfOctets.equals(other.numberOfOctets)) + return false; + if (!Arrays.equals(path, other.path)) + return false; + if (sectionType != other.sectionType) + return false; + return true; + } + + } Modified: james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/FetchData.java URL: http://svn.apache.org/viewvc/james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/FetchData.java?rev=641587&r1=641586&r2=641587&view=diff ============================================================================== --- james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/FetchData.java (original) +++ james/server/trunk/imap-api/src/main/java/org/apache/james/api/imap/message/FetchData.java Wed Mar 26 14:34:56 2008 @@ -108,4 +108,51 @@ } bodyElements.add(element); } + + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (body ? 1231 : 1237); + result = PRIME * result + ((bodyElements == null) ? 0 : bodyElements.hashCode()); + result = PRIME * result + (bodyStructure ? 1231 : 1237); + result = PRIME * result + (envelope ? 1231 : 1237); + result = PRIME * result + (flags ? 1231 : 1237); + result = PRIME * result + (internalDate ? 1231 : 1237); + result = PRIME * result + (setSeen ? 1231 : 1237); + result = PRIME * result + (size ? 1231 : 1237); + result = PRIME * result + (uid ? 1231 : 1237); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final FetchData other = (FetchData) obj; + if (body != other.body) + return false; + if (bodyElements == null) { + if (other.bodyElements != null) + return false; + } else if (!bodyElements.equals(other.bodyElements)) + return false; + if (bodyStructure != other.bodyStructure) + return false; + if (envelope != other.envelope) + return false; + if (flags != other.flags) + return false; + if (internalDate != other.internalDate) + return false; + if (setSeen != other.setSeen) + return false; + if (size != other.size) + return false; + if (uid != other.uid) + return false; + return true; + } } Modified: james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/base/AbstractImapCommandParser.java URL: http://svn.apache.org/viewvc/james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/base/AbstractImapCommandParser.java?rev=641587&r1=641586&r2=641587&view=diff ============================================================================== --- james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/base/AbstractImapCommandParser.java (original) +++ james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/base/AbstractImapCommandParser.java Wed Mar 26 14:34:56 2008 @@ -472,10 +472,42 @@ */ public long number( ImapRequestLineReader request ) throws ProtocolException { - String digits = consumeWord( request, new DigitCharValidator() ); - return Long.parseLong( digits ); + return readDigits(request, 0, 0, true); } - + + private long readDigits( final ImapRequestLineReader request, int add, final long total, final boolean first ) throws ProtocolException + { + final char next; + if (first) { + next = request.nextWordChar(); + } else { + request.consume(); + next = request.nextChar(); + } + final long currentTotal = (10 * total) + add; + switch (next) { + case '0': return readDigits(request, 0, currentTotal, false); + case '1': return readDigits(request, 1, currentTotal, false); + case '2': return readDigits(request, 2, currentTotal, false); + case '3': return readDigits(request, 3, currentTotal, false); + case '4': return readDigits(request, 4, currentTotal, false); + case '5': return readDigits(request, 5, currentTotal, false); + case '6': return readDigits(request, 6, currentTotal, false); + case '7': return readDigits(request, 7, currentTotal, false); + case '8': return readDigits(request, 8, currentTotal, false); + case '9': return readDigits(request, 9, currentTotal, false); + case '.': + case ' ': + case '>': + case '\r': + case '\n': + case '\t': + return currentTotal; + default: + throw new ProtocolException("Expected a digit but was " + next); + } + } + /** * Reads an argument of type "nznumber" (a non-zero number) * (NOTE this isn't strictly as per the spec, since the spec disallows @@ -612,15 +644,6 @@ chr == '{' || chr == ' ' || chr == Character.CONTROL ); - } - } - - public static class DigitCharValidator implements CharacterValidator - { - public boolean isValid( char chr ) - { - return ( ( chr >= '0' && chr <= '9' ) || - chr == '*' ); } } Modified: james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java URL: http://svn.apache.org/viewvc/james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java?rev=641587&r1=641586&r2=641587&view=diff ============================================================================== --- james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java (original) +++ james/server/trunk/imap-codec-library/src/main/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParser.java Wed Mar 26 14:34:56 2008 @@ -45,9 +45,9 @@ final ImapCommand command = factory.getFetch(); setCommand(command); } - + public FetchData fetchRequest( ImapRequestLineReader request ) - throws ProtocolException + throws ProtocolException { FetchData fetch = new FetchData(); @@ -63,81 +63,90 @@ consumeChar(request, ')'); } else { addNextElement( request, fetch ); - + } return fetch; } - private void addNextElement( ImapRequestLineReader command, FetchData fetch) - throws ProtocolException - { - /*char next = nextCharInLine( command ); - StringBuffer element = new StringBuffer(); - while ( next != ' ' && next != '[' && next != ')' && next!='\n' && next!='\r' ) { - element.append(next); - command.consume(); - next = nextCharInLine( command ); - }*/ - - - //String name = element.toString(); - String name = readWord(command, " [)\r\n"); - char next = command.nextChar(); - // Simple elements with no '[]' parameters. - //if ( next == ' ' || next == ')' || next == '\n' || next == '\r') { - if (next != '[') { - if ( "FAST".equalsIgnoreCase( name ) ) { - fetch.setFlags(true); - fetch.setInternalDate(true); - fetch.setSize(true); - } else if ("FULL".equalsIgnoreCase(name)) { - fetch.setFlags(true); - fetch.setInternalDate(true); - fetch.setSize(true); - fetch.setEnvelope(true); - fetch.setBody(true); - } else if ("ALL".equalsIgnoreCase(name)) { - fetch.setFlags(true); - fetch.setInternalDate(true); - fetch.setSize(true); - fetch.setEnvelope(true); - } else if ("FLAGS".equalsIgnoreCase(name)) { - fetch.setFlags(true); - } else if ("RFC822.SIZE".equalsIgnoreCase(name)) { - fetch.setSize(true); - } else if ("ENVELOPE".equalsIgnoreCase(name)) { - fetch.setEnvelope(true); - } else if ("INTERNALDATE".equalsIgnoreCase(name)) { - fetch.setInternalDate(true); - } else if ("BODY".equalsIgnoreCase(name)) { - fetch.setBody(true); - } else if ("BODYSTRUCTURE".equalsIgnoreCase(name)) { - fetch.setBodyStructure(true); - } else if ("UID".equalsIgnoreCase(name)) { - fetch.setUid(true); - } else if ("RFC822".equalsIgnoreCase(name)) { - fetch.add(BodyFetchElement.createRFC822(), false); - } else if ("RFC822.HEADER".equalsIgnoreCase(name)) { - fetch.add(BodyFetchElement.createRFC822Header(), true); - } else if ("RFC822.TEXT".equalsIgnoreCase(name)) { - fetch.add(BodyFetchElement.createRFC822Text(), false); - } else { - throw new ProtocolException( "Invalid fetch attribute: " + name ); - } + private void addNextElement( ImapRequestLineReader reader, FetchData fetch) + throws ProtocolException + { + //String name = element.toString(); + String name = readWord(reader, " [)\r\n"); + char next = reader.nextChar(); + // Simple elements with no '[]' parameters. + if (next != '[') { + if ( "FAST".equalsIgnoreCase( name ) ) { + fetch.setFlags(true); + fetch.setInternalDate(true); + fetch.setSize(true); + } else if ("FULL".equalsIgnoreCase(name)) { + fetch.setFlags(true); + fetch.setInternalDate(true); + fetch.setSize(true); + fetch.setEnvelope(true); + fetch.setBody(true); + } else if ("ALL".equalsIgnoreCase(name)) { + fetch.setFlags(true); + fetch.setInternalDate(true); + fetch.setSize(true); + fetch.setEnvelope(true); + } else if ("FLAGS".equalsIgnoreCase(name)) { + fetch.setFlags(true); + } else if ("RFC822.SIZE".equalsIgnoreCase(name)) { + fetch.setSize(true); + } else if ("ENVELOPE".equalsIgnoreCase(name)) { + fetch.setEnvelope(true); + } else if ("INTERNALDATE".equalsIgnoreCase(name)) { + fetch.setInternalDate(true); + } else if ("BODY".equalsIgnoreCase(name)) { + fetch.setBody(true); + } else if ("BODYSTRUCTURE".equalsIgnoreCase(name)) { + fetch.setBodyStructure(true); + } else if ("UID".equalsIgnoreCase(name)) { + fetch.setUid(true); + } else if ("RFC822".equalsIgnoreCase(name)) { + fetch.add(BodyFetchElement.createRFC822(), false); + } else if ("RFC822.HEADER".equalsIgnoreCase(name)) { + fetch.add(BodyFetchElement.createRFC822Header(), true); + } else if ("RFC822.TEXT".equalsIgnoreCase(name)) { + fetch.add(BodyFetchElement.createRFC822Text(), false); + } else { + throw new ProtocolException( "Invalid fetch attribute: " + name ); } - else { - consumeChar( command, '[' ); + } + else { + consumeChar( reader, '[' ); + + String parameter = readWord(reader, "]"); - String parameter = readWord(command, "]"); + consumeChar( reader, ']'); - consumeChar( command, ']'); - - final BodyFetchElement bodyFetchElement = createBodyElement(parameter); - final boolean isPeek = isPeek(name); - fetch.add(bodyFetchElement, isPeek); + final Long firstOctet; + final Long numberOfOctets; + if(reader.nextChar() == '<') { + consumeChar(reader, '<'); + firstOctet = new Long(number(reader)); + if (reader.nextChar() == '.') { + consumeChar(reader, '.'); + numberOfOctets = new Long(nzNumber(reader)); + } else { + numberOfOctets = null; + } + consumeChar(reader, '>'); + } else { + firstOctet = null; + numberOfOctets = null; } + + + final BodyFetchElement bodyFetchElement + = createBodyElement(parameter, firstOctet, numberOfOctets); + final boolean isPeek = isPeek(name); + fetch.add(bodyFetchElement, isPeek); } + } private boolean isPeek(String name) throws ProtocolException { final boolean isPeek; @@ -151,16 +160,15 @@ return isPeek; } - private BodyFetchElement createBodyElement(String parameter) throws ProtocolException { + private BodyFetchElement createBodyElement(String parameter, Long firstOctet, Long numberOfOctets) throws ProtocolException { final String responseName = "BODY[" + parameter + "]"; FetchPartPathDecoder decoder = new FetchPartPathDecoder(); decoder.decode(parameter); final int sectionType = getSectionType(decoder); - + final List names = decoder.getNames(); final int[] path = decoder.getPath(); - final BodyFetchElement bodyFetchElement - = new BodyFetchElement(responseName, sectionType, path, names); + final BodyFetchElement bodyFetchElement = new BodyFetchElement(responseName, sectionType, path, names, firstOctet, numberOfOctets); return bodyFetchElement; } @@ -202,19 +210,9 @@ } return buf.toString(); } - - private char nextCharInLine( ImapRequestLineReader request ) - throws ProtocolException - { - char next = request.nextChar(); - if ( next == '\r' || next == '\n' ) { - throw new ProtocolException( "Unexpected end of line." ); - } - return next; - } private char nextNonSpaceChar( ImapRequestLineReader request ) - throws ProtocolException + throws ProtocolException { char next = request.nextChar(); while ( next == ' ' ) { @@ -224,11 +222,12 @@ return next; } - protected ImapMessage decode(ImapCommand command, ImapRequestLineReader request, String tag, boolean useUids) throws ProtocolException { + protected ImapMessage decode(ImapCommand command, ImapRequestLineReader request, + String tag, boolean useUids) throws ProtocolException { IdRange[] idSet = parseIdRange( request ); FetchData fetch = fetchRequest( request ); endLine( request ); - + final Imap4Rev1MessageFactory factory = getMessageFactory(); final ImapMessage result = factory.createFetchMessage(command, useUids, idSet, fetch, tag); return result; Added: james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParserPartialFetchTest.java URL: http://svn.apache.org/viewvc/james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParserPartialFetchTest.java?rev=641587&view=auto ============================================================================== --- james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParserPartialFetchTest.java (added) +++ james/server/trunk/imap-codec-library/src/test/java/org/apache/james/imapserver/codec/decode/imap4rev1/FetchCommandParserPartialFetchTest.java Wed Mar 26 14:34:56 2008 @@ -0,0 +1,101 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT 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.james.imapserver.codec.decode.imap4rev1; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import org.apache.james.api.imap.ImapCommand; +import org.apache.james.api.imap.ImapMessage; +import org.apache.james.api.imap.ProtocolException; +import org.apache.james.api.imap.imap4rev1.Imap4Rev1CommandFactory; +import org.apache.james.api.imap.imap4rev1.Imap4Rev1MessageFactory; +import org.apache.james.api.imap.message.BodyFetchElement; +import org.apache.james.api.imap.message.FetchData; +import org.apache.james.api.imap.message.IdRange; +import org.apache.james.imapserver.codec.decode.ImapRequestLineReader; +import org.jmock.Mock; +import org.jmock.MockObjectTestCase; +import org.jmock.core.Constraint; + +public class FetchCommandParserPartialFetchTest extends MockObjectTestCase { + + FetchCommandParser parser; + Mock mockCommandFactory; + Mock mockMessageFactory; + Mock mockCommand; + Mock mockMessage; + ImapCommand command; + ImapMessage message; + + protected void setUp() throws Exception { + super.setUp(); + parser = new FetchCommandParser(); + mockCommandFactory = mock(Imap4Rev1CommandFactory.class); + mockCommandFactory.expects(once()).method("getFetch"); + mockMessageFactory = mock(Imap4Rev1MessageFactory.class); + mockCommand = mock(ImapCommand.class); + command = (ImapCommand) mockCommand.proxy(); + mockMessage = mock(ImapMessage.class); + message = (ImapMessage) mockMessage.proxy(); + parser.init((Imap4Rev1CommandFactory) mockCommandFactory.proxy()); + parser.setMessageFactory((Imap4Rev1MessageFactory) mockMessageFactory.proxy()); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testShouldParseZeroAndLength() throws Exception { + IdRange[] ranges = {new IdRange(1)}; + FetchData data = new FetchData(); + data.add(new BodyFetchElement("BODY[]", BodyFetchElement.CONTENT, null, null, + new Long(0), new Long(100)), false); + check("1 (BODY[]<0.100>)\r\n", ranges, false, data, "A01"); + } + + public void testShouldParseNonZeroAndLength() throws Exception { + IdRange[] ranges = {new IdRange(1)}; + FetchData data = new FetchData(); + data.add(new BodyFetchElement("BODY[]", BodyFetchElement.CONTENT, null, null, + new Long(20), new Long(12342348)), false); + check("1 (BODY[]<20.12342348>)\r\n", ranges, false, data, "A01"); + } + + public void testShouldNotParseZeroLength() throws Exception { + try { + ImapRequestLineReader reader = new ImapRequestLineReader(new ByteArrayInputStream("1 (BODY[]<20.0>)\r\n".getBytes("US-ASCII")), + new ByteArrayOutputStream()); + parser.decode(command, reader, "A01", false); + fail("Number of octets must be non-zero"); + } catch (ProtocolException e) { + // expected + } + } + + + private void check(String input, final IdRange[] idSet, final boolean useUids, FetchData data, String tag) throws Exception { + ImapRequestLineReader reader = new ImapRequestLineReader(new ByteArrayInputStream(input.getBytes("US-ASCII")), + new ByteArrayOutputStream()); + Constraint[] constraints = {eq(command), eq(useUids), eq(idSet), eq(data), same(tag)}; + mockMessageFactory.expects(once()).method("createFetchMessage").with(constraints).will(returnValue(message)); + parser.decode(command, reader, tag, useUids); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]