blob: cb2dc35688787e28a6fb2b95c2271b47878a53b3 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.geronimo.javamail.store.imap.connection;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.util.ResponseFormatException;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
/**
* Class to represent a FETCH response BODY segment qualifier. The qualifier is
* of the form "BODY[<section>]<<partial>>". The optional section qualifier is
* a "." separated part specifiers. A part specifier is either a number, or
* one of the tokens HEADER, HEADER.FIELD, HEADER.FIELD.NOT, MIME, and TEXT.
* The partial specification is in the form "<start.length>".
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPBodySection {
// the section type qualifiers
static public final int BODY = 0;
static public final int HEADERS = 1;
static public final int HEADERSUBSET = 2;
static public final int MIME = 3;
static public final int TEXT = 4;
// the optional part number
public String partNumber = "1";
// the string name of the section
public String sectionName = "";
// the section qualifier
public int section;
// the starting substring position
public int start = -1;
// the substring length (requested)
public int length = -1;
// the list of any explicit header names
public List headers = null;
/**
* Construct a simple-toplevel BodySection tag.
*
* @param section The section identifier.
*/
public IMAPBodySection(int section) {
this.section = section;
partNumber = "1";
start = -1;
length = -1;
}
/**
* construct a BodySegment descriptor from the FETCH returned name.
*
* @param name The name code, which may be encoded with a section identifier and
* substring qualifiers.
*
* @exception MessagingException
*/
public IMAPBodySection(IMAPResponseTokenizer source) throws MessagingException {
// this could be just "BODY" alone.
if (!source.peek(false, true).isType('[')) {
// complete body, all other fields take default
section = BODY;
return;
}
// now we need to scan along this, building up the pieces as we go.
// NOTE: The section identifiers use "[", "]", "." as delimiters, which
// are normally acceptable in ATOM names. We need to use the expanded
// delimiter set to parse these tokens off.
Token token = source.next(false, true);
// the first token was the "[", now step to the next token in line.
token = source.next(false, true);
if (token.isType(Token.NUMERIC)) {
token = parsePartNumber(token, source);
}
// have a potential name here?
if (token.isType(Token.ATOM)) {
token = parseSectionName(token, source);
}
// the HEADER.FIELD and HEADER.FIELD.NOT section types
// are followed by a list of header names.
if (token.isType('(')) {
token = parseHeaderList(source);
}
// ok, in theory, our current token should be a ']'
if (!token.isType(']')) {
throw new ResponseFormatException("Invalid section identifier on FETCH response");
}
// do we have a substring qualifier?
// that needs to be stripped off too
parseSubstringValues(source);
// now fill in the type information
if (sectionName.equals("")) {
section = BODY;
}
else if (sectionName.equals("HEADER")) {
section = HEADERS;
}
else if (sectionName.equals("HEADER.FIELDS")) {
section = HEADERSUBSET;
}
else if (sectionName.equals("HEADER.FIELDS.NOT")) {
section = HEADERSUBSET;
}
else if (sectionName.equals("TEXT")) {
section = TEXT;
}
else if (sectionName.equals("MIME")) {
section = MIME;
}
}
/**
* Strip the part number off of a BODY section identifier. The part number
* is a series of "." separated tokens. So "BODY[3.2.1]" would be the BODY for
* section 3.2.1 of a multipart message. The section may also have a qualifier
* name on the end. "BODY[3.2.1.HEADER}" would be the HEADERS for that
* body section. The return value is the name of the section, which can
* be a "" or the the section qualifier (e.g., "HEADER").
*
* @param name The section name.
*
* @return The remainder of the section name after the numeric part number has
* been removed.
*/
private Token parsePartNumber(Token token, IMAPResponseTokenizer source) throws MessagingException {
StringBuffer part = new StringBuffer(token.getValue());
// NB: We're still parsing with the expanded delimiter set
token = source.next(false, true);
while (true) {
// Not a period? We've reached the end of the section number,
// finalize the part number and let the caller figure out what
// to do from here.
if (!token.isType('.')) {
partNumber = part.toString();
return token;
}
// might have another number section
else {
// step to the next token
token = source.next(false, true);
// another section number piece?
if (token.isType(Token.NUMERIC)) {
// add this to the collection, and continue
part.append('.');
part.append(token.getValue());
token = source.next(false, true);
}
else {
partNumber = part.toString();
// this is likely the start of the section name
return token;
}
}
}
}
/**
* Parse the section name, if any, in a BODY section qualifier. The
* section name may stand alone within the body section (e.g.,
* "BODY[HEADERS]" or follow the section number (e.g.,
* "BODY[1.2.3.HEADERS.FIELDS.NOT]".
*
* @param token The first token of the name sequence.
* @param source The source tokenizer.
*
* @return The first non-name token in the response.
*/
private Token parseSectionName(Token token, IMAPResponseTokenizer source) throws MessagingException {
StringBuffer part = new StringBuffer(token.getValue());
// NB: We're still parsing with the expanded delimiter set
token = source.next(false, true);
while (true) {
// Not a period? We've reached the end of the section number,
// finalize the part number and let the caller figure out what
// to do from here.
if (!token.isType('.')) {
sectionName = part.toString();
return token;
}
// might have another number section
else {
// add this to the collection, and continue
part.append('.');
part.append(source.readString());
token = source.next(false, true);
}
}
}
/**
* Parse a header list that may follow the HEADER.FIELD or HEADER.FIELD.NOT
* name qualifier. This is a list of string values enclosed in parens.
*
* @param source The source tokenizer.
*
* @return The next token in the response (which should be the section terminator, ']')
* @exception MessagingException
*/
private Token parseHeaderList(IMAPResponseTokenizer source) throws MessagingException {
headers = new ArrayList();
// normal parsing rules going on here
while (source.notListEnd()) {
String value = source.readString();
headers.add(value);
}
// step over the closing paren
source.next();
// NB, back to the expanded token rules again
return source.next(false, true);
}
/**
* Parse off the substring values following the section identifier, if
* any. If present, they will be in the format "<start.len>".
*
* @param source The source tokenizer.
*
* @exception MessagingException
*/
private void parseSubstringValues(IMAPResponseTokenizer source) throws MessagingException {
// We rarely have one of these, so it's a quick out
if (!source.peek(false, true).isType('<')) {
return;
}
// step over the angle bracket.
source.next(false, true);
// pull out the start information
start = source.next(false, true).getInteger();
// step over the period
source.next(false, true);
// now the length bit
length = source.next(false, true).getInteger();
// and consume the closing angle bracket
source.next(false, true);
}
}