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