View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.store.imap.connection;
19  
20  import java.util.List;
21  import java.util.ArrayList;
22  import java.util.StringTokenizer;
23  
24  import javax.mail.MessagingException;
25  
26  import org.apache.geronimo.javamail.util.ResponseFormatException; 
27  import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
28  
29  /**
30   * Class to represent a FETCH response BODY segment qualifier.  The qualifier is 
31   * of the form "BODY[<section>]<<partial>>".  The optional section qualifier is 
32   * a "." separated part specifiers.  A part specifier is either a number, or 
33   * one of the tokens HEADER, HEADER.FIELD, HEADER.FIELD.NOT, MIME, and TEXT.  
34   * The partial specification is in the form "<start.length>". 
35   *
36   * @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
37   */
38  public class IMAPBodySection {
39      // the section type qualifiers 
40      static public final int BODY = 0; 
41      static public final int HEADERS = 1; 
42      static public final int HEADERSUBSET = 2; 
43      static public final int MIME = 3;
44      static public final int TEXT = 4; 
45      
46      // the optional part number 
47      public String partNumber = "1"; 
48      // the string name of the section 
49      public String sectionName = ""; 
50      // the section qualifier 
51      public int section; 
52      // the starting substring position 
53      public int start = -1; 
54      // the substring length (requested)
55      public int length = -1; 
56      // the list of any explicit header names 
57      public List headers = null; 
58      
59      /**
60       * Construct a simple-toplevel BodySection tag.
61       * 
62       * @param section The section identifier.
63       */
64      public IMAPBodySection(int section) {
65          this.section = section; 
66          partNumber = "1"; 
67          start = -1; 
68          length = -1; 
69      }
70      
71      /**
72       * construct a BodySegment descriptor from the FETCH returned name. 
73       * 
74       * @param name   The name code, which may be encoded with a section identifier and
75       *               substring qualifiers.
76       * 
77       * @exception MessagingException
78       */
79      public IMAPBodySection(IMAPResponseTokenizer source) throws MessagingException {
80          
81          // this could be just "BODY" alone.  
82          if (!source.peek(false, true).isType('[')) {
83              // complete body, all other fields take default  
84              section = BODY;             
85              return; 
86          }
87          
88          // now we need to scan along this, building up the pieces as we go. 
89          // NOTE:  The section identifiers use "[", "]", "." as delimiters, which 
90          // are normally acceptable in ATOM names.  We need to use the expanded 
91          // delimiter set to parse these tokens off. 
92          Token token = source.next(false, true); 
93          // the first token was the "[", now step to the next token in line. 
94          token = source.next(false, true); 
95          
96          if (token.isType(Token.NUMERIC)) {
97              token = parsePartNumber(token, source); 
98          }
99          
100         // have a potential name here?
101         if (token.isType(Token.ATOM)) {
102             token = parseSectionName(token, source); 
103         }
104         
105         // the HEADER.FIELD and HEADER.FIELD.NOT section types 
106         // are followed by a list of header names. 
107         if (token.isType('(')) {
108             token = parseHeaderList(source); 
109         }
110         
111         // ok, in theory, our current token should be a ']'
112         if (!token.isType(']')) {
113             throw new ResponseFormatException("Invalid section identifier on FETCH response"); 
114         }
115         
116         // do we have a substring qualifier?
117         // that needs to be stripped off too 
118         parseSubstringValues(source); 
119         
120         // now fill in the type information 
121         if (sectionName.equals("")) {
122             section = BODY; 
123         }
124         else if (sectionName.equals("HEADER")) {
125             section = HEADERS; 
126         }
127         else if (sectionName.equals("HEADER.FIELDS")) {
128             section = HEADERSUBSET; 
129         }
130         else if (sectionName.equals("HEADER.FIELDS.NOT")) {
131             section = HEADERSUBSET; 
132         }
133         else if (sectionName.equals("TEXT")) {
134             section = TEXT; 
135         }
136         else if (sectionName.equals("MIME")) {
137             section = MIME; 
138         }
139     }
140     
141     
142     /**
143      * Strip the part number off of a BODY section identifier.  The part number 
144      * is a series of "." separated tokens.  So "BODY[3.2.1]" would be the BODY for 
145      * section 3.2.1 of a multipart message.  The section may also have a qualifier
146      * name on the end.  "BODY[3.2.1.HEADER}" would be the HEADERS for that 
147      * body section.  The return value is the name of the section, which can 
148      * be a "" or the the section qualifier (e.g., "HEADER"). 
149      * 
150      * @param name   The section name.
151      * 
152      * @return The remainder of the section name after the numeric part number has 
153      *         been removed.
154      */
155     private Token parsePartNumber(Token token, IMAPResponseTokenizer source) throws MessagingException {
156         StringBuffer part = new StringBuffer(token.getValue()); 
157         // NB:  We're still parsing with the expanded delimiter set 
158         token = source.next(false, true); 
159         
160         while (true) {
161             // Not a period?  We've reached the end of the section number, 
162             // finalize the part number and let the caller figure out what 
163             // to do from here.  
164             if (!token.isType('.')) {
165                 partNumber = part.toString(); 
166                 return token; 
167             }
168             // might have another number section 
169             else {
170                 // step to the next token 
171                 token = source.next(false, true); 
172                 // another section number piece?
173                 if (token.isType(Token.NUMERIC)) {
174                     // add this to the collection, and continue 
175                     part.append('.'); 
176                     part.append(token.getValue()); 
177                     token = source.next(false, true); 
178                 }
179                 else  {
180                     partNumber = part.toString(); 
181                     // this is likely the start of the section name 
182                     return token; 
183                 }
184             }
185         }
186     }
187     
188     
189     /**
190      * Parse the section name, if any, in a BODY section qualifier.  The 
191      * section name may stand alone within the body section (e.g., 
192      * "BODY[HEADERS]" or follow the section number (e.g., 
193      * "BODY[1.2.3.HEADERS.FIELDS.NOT]".  
194      * 
195      * @param token  The first token of the name sequence.
196      * @param source The source tokenizer.
197      * 
198      * @return The first non-name token in the response. 
199      */
200     private Token parseSectionName(Token token, IMAPResponseTokenizer source) throws MessagingException {
201         StringBuffer part = new StringBuffer(token.getValue()); 
202         // NB:  We're still parsing with the expanded delimiter set 
203         token = source.next(false, true); 
204         
205         while (true) {
206             // Not a period?  We've reached the end of the section number, 
207             // finalize the part number and let the caller figure out what 
208             // to do from here.  
209             if (!token.isType('.')) {
210                 sectionName = part.toString(); 
211                 return token; 
212             }
213             // might have another number section 
214             else {
215                 // add this to the collection, and continue 
216                 part.append('.'); 
217                 part.append(source.readString()); 
218                 token = source.next(false, true); 
219             }
220         }
221     }
222     
223     
224     /**
225      * Parse a header list that may follow the HEADER.FIELD or HEADER.FIELD.NOT
226      * name qualifier.  This is a list of string values enclosed in parens.
227      * 
228      * @param source The source tokenizer.
229      * 
230      * @return The next token in the response (which should be the section terminator, ']')
231      * @exception MessagingException
232      */
233     private Token parseHeaderList(IMAPResponseTokenizer source) throws MessagingException {
234         headers = new ArrayList();
235         
236         // normal parsing rules going on here 
237         while (source.notListEnd()) {
238             String value = source.readString();
239             headers.add(value);
240         }
241         // step over the closing paren 
242         source.next(); 
243         // NB, back to the expanded token rules again 
244         return source.next(false, true); 
245     }
246     
247     
248     /**
249      * Parse off the substring values following the section identifier, if 
250      * any.  If present, they will be in the format "<start.len>".  
251      * 
252      * @param source The source tokenizer.
253      * 
254      * @exception MessagingException
255      */
256     private void parseSubstringValues(IMAPResponseTokenizer source) throws MessagingException {
257         // We rarely have one of these, so it's a quick out 
258         if (!source.peek(false, true).isType('<')) {
259             return; 
260         }
261         // step over the angle bracket. 
262         source.next(false, true); 
263         // pull out the start information 
264         start = source.next(false, true).getInteger(); 
265         // step over the period 
266         source.next(false, true);         
267         // now the length bit                  
268         length = source.next(false, true).getInteger(); 
269         // and consume the closing angle bracket 
270         source.next(false, true); 
271     }
272 }
273