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