Skip to content

Package: MessageLoader

MessageLoader

nameinstructionbranchcomplexitylinemethod
MessageLoader(TempFile)
M: 15 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
fill()
M: 22 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
get()
M: 33 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
isPrefix(String, String)
M: 9 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
load(FileDescriptor, long, List)
M: 138 C: 0
0%
M: 16 C: 0
0%
M: 9 C: 0
0%
M: 39 C: 0
0%
M: 1 C: 0
0%
skip(int)
M: 70 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 13 C: 0
0%
M: 1 C: 0
0%
skipBody()
M: 87 C: 0
0%
M: 26 C: 0
0%
M: 14 C: 0
0%
M: 20 C: 0
0%
M: 1 C: 0
0%
skipHeader(boolean)
M: 210 C: 0
0%
M: 60 C: 0
0%
M: 31 C: 0
0%
M: 50 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.mbox;
18:
19: import java.io.*;
20: import java.util.*;
21:
22: /**
23: * A support class that contains the state and logic needed when
24: * loading messages from a folder.
25: */
26: final class MessageLoader {
27: private final TempFile temp;
28: private FileInputStream fis = null;
29: private OutputStream fos = null;
30: private int pos, len;        // position in and length of buffer
31: private long off;                // current offset in temp file
32: private long prevend;        // the end of the previous message in temp file
33: private MboxFolder.MessageMetadata md;
34: private byte[] buf = null;
35: // the length of the longest header we'll need to look at
36: private static final int LINELEN = "Content-Length: XXXXXXXXXX".length();
37: private char[] line;
38:
39: public MessageLoader(TempFile temp) {
40:         this.temp = temp;
41: }
42:
43: /**
44: * Load messages from the given file descriptor, starting at the
45: * specified offset, adding the MessageMetadata to the list. <p>
46: *
47: * The data is assumed to be in UNIX mbox format, with newlines
48: * only as the line terminators.
49: */
50: public int load(FileDescriptor fd, long offset,
51:                                 List<MboxFolder.MessageMetadata> msgs)
52:                                 throws IOException {
53:         // XXX - could allocate and deallocate buffers here
54:         int loaded = 0;
55:         try {
56:          fis = new FileInputStream(fd);
57:•         if (fis.skip(offset) != offset)
58:                 throw new EOFException("Failed to skip to offset " + offset);
59:          this.off = prevend = temp.length();
60:          pos = len = 0;
61:          line = new char[LINELEN];
62:          buf = new byte[64 * 1024];
63:          fos = temp.getAppendStream();
64:          int n;
65:          // keep loading messages as long as we have headers
66:•         while ((n = skipHeader(loaded == 0)) >= 0) {
67:                 long start;
68:•                if (n == 0) {
69:                  // didn't find a Content-Length, skip the body
70:                  start = skipBody();
71:•                 if (start < 0) {
72:                         // md.end = -1;
73:                         md.dataend = -1;
74:                         msgs.add(md);
75:                         loaded++;
76:                         break;
77:                  }
78:                  md.dataend = start;
79:                 } else {
80:                  // skip over the body
81:                  skip(n);
82:                  md.dataend = off;
83:                  int b;
84:                  // skip any blank lines after the body
85:•                 while ((b = get()) >= 0) {
86:•                        if (b != '\n')
87:                          break;
88:                  }
89:                  start = off;
90:•                 if (b >= 0)
91:                         start--;        // back up one byte if not at EOF
92:                 }
93:                 // md.end = start;
94:                 prevend = start;
95:                 msgs.add(md);
96:                 loaded++;
97:          }
98:         } finally {
99:          try {
100:                 fis.close();
101:          } catch (IOException ex) {
102:                 // ignore
103:          }
104:          try {
105:                 fos.close();
106:          } catch (IOException ex) {
107:                 // ignore
108:          }
109:          line = null;
110:          buf = null;
111:         }
112:         return loaded;
113: }
114:
115: /**
116: * Skip over the message header, returning the content length
117: * of the body, or 0 if no Content-Length header was seen.
118: * Update the MessageMetadata based on the headers seen.
119: * return -1 on EOF.
120: */
121: private int skipHeader(boolean first) throws IOException {
122:         int clen = 0;
123:         boolean bol = true;
124:         int lpos = -1;
125:         int b;
126:         boolean saw_unix_from = false;
127:         int lineno = 0;
128:         md = new MboxFolder.MessageMetadata();
129:         md.start = prevend;
130:         md.recent = true;
131:•        while ((b = get()) >= 0) {
132:•         if (bol) {
133:•                if (b == '\n')
134:                  break;
135:                 lpos = 0;
136:          }
137:•         if (b == '\n') {
138:                 bol = true;
139:                 lineno++;
140:                 // newline at end of line, was the line one of the headers
141:                 // we're looking for?
142:•                if (lpos > 7) {
143:                  // XXX - make this more efficient?
144:                  String s = new String(line, 0, lpos);
145:                  // fast check for Content-Length header
146:•                 if (lineno == 1 && line[0] == 'F' && isPrefix(s, "From ")) {
147:                         saw_unix_from = true;
148:•                 } else if (line[7] == '-' &&
149:•                                isPrefix(s, "Content-Length:")) {
150:                         s = s.substring(15).trim();
151:                         try {
152:                          clen = Integer.parseInt(s);
153:                         } catch (NumberFormatException ex) {
154:                          // ignore it
155:                         }
156:                  // fast check for Status header
157:•                 } else if ((line[1] == 't' || line[1] == 'T') &&
158:•                                isPrefix(s, "Status:")) {
159:•                        if (s.indexOf('O') >= 0)
160:                          md.recent = false;
161:                  // fast check for X-Status header
162:•                 } else if ((line[3] == 't' || line[3] == 'T') &&
163:•                                isPrefix(s, "X-Status:")) {
164:•                        if (s.indexOf('D') >= 0)
165:                          md.deleted = true;
166:                  // fast check for X-Dt-Delete-Time header
167:•                 } else if (line[4] == '-' &&
168:•                                isPrefix(s, "X-Dt-Delete-Time:")) {
169:                         md.deleted = true;
170:                  // fast check for X-IMAP header
171:•                 } else if (line[5] == 'P' && s.startsWith("X-IMAP:")) {
172:                         md.imap = true;
173:                  }
174:                 }
175:          } else {
176:                 // accumlate data in line buffer
177:                 bol = false;
178:•                if (lpos < 0)        // ignoring this line
179:                  continue;
180:•                if (lpos == 0 && (b == ' ' || b == '\t'))
181:                  lpos = -1;        // ignore continuation lines
182:•                else if (lpos < line.length)
183:                  line[lpos++] = (char)b;
184:          }
185:         }
186:
187:         // if we hit EOF, or this is the first message we're loading and
188:         // it doesn't have a UNIX From line, return EOF.
189:         // (After the first message, UNIX From lines are seen by skipBody
190:         // to terminate the message.)
191:•        if (b < 0 || (first && !saw_unix_from))
192:          return -1;
193:         else
194:          return clen;
195: }
196:
197: /**
198: * Does "s" start with "pre", ignoring case?
199: */
200: private static final boolean isPrefix(String s, String pre) {
201:         return s.regionMatches(true, 0, pre, 0, pre.length());
202: }
203:
204: /**
205: * Skip over the body of the message looking for a line that starts
206: * with "From ". If found, return the offset of the beginning of
207: * that line. Return -1 on EOF.
208: */
209: private long skipBody() throws IOException {
210:         boolean bol = true;
211:         int lpos = -1;
212:         long loff = off;
213:         int b;
214:•        while ((b = get()) >= 0) {
215:•         if (bol) {
216:                 lpos = 0;
217:                 loff = off - 1;
218:          }
219:•         if (b == '\n') {
220:                 bol = true;
221:•                if (lpos >= 5) {        // have enough data to test?
222:•                 if (line[0] == 'F' && line[1] == 'r' && line[2] == 'o' &&
223:                         line[3] == 'm' && line[4] == ' ')
224:                         return loff;
225:                 }
226:          } else {
227:                 bol = false;
228:•                if (lpos < 0)
229:                  continue;
230:•                if (lpos == 0 && b != 'F')
231:                  lpos = -1;                // ignore lines that don't start with F
232:•                else if (lpos < 5)        // only need first 5 chars to test
233:                  line[lpos++] = (char)b;
234:          }
235:         }
236:         return -1;
237: }
238:
239: /**
240: * Skip "n" bytes, returning how much we were able to skip.
241: */
242: private final int skip(int n) throws IOException {
243:         int n0 = n;
244:•        if (pos + n < len) {
245:          pos += n;        // can do it all within this buffer
246:          off += n;
247:         } else {
248:          do {
249:                 n -= (len - pos);        // skip rest of this buffer
250:                 off += (len - pos);
251:                 fill();
252:•                if (len <= 0)        // ran out of data
253:                  return n0 - n;
254:•         } while (n > len);
255:          pos += n;
256:          off += n;
257:         }
258:         return n0;
259: }
260:
261: /**
262: * Return the next byte.
263: */
264: private final int get() throws IOException {
265:•        if (pos >= len)
266:          fill();
267:•        if (pos >= len)
268:          return -1;
269:         else {
270:          off++;
271:          return buf[pos++] & 0xff;
272:         }
273: }
274:
275: /**
276: * Fill our buffer with more data.
277: * Every buffer we read is also written to the temp file.
278: */
279: private final void fill() throws IOException {
280:         len = fis.read(buf);
281:         pos = 0;
282:•        if (len > 0)
283:          fos.write(buf, 0, len);
284: }
285: }