| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT 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); |
| } |
| } |
| |