--- /dev/null
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A support class that contains the state and logic needed when
+ * loading messages from a folder.
+ */
+final class MessageLoader {
+ private final TempFile temp;
+ private FileInputStream fis = null;
+ private OutputStream fos = null;
+ private int pos, len; // position in and length of buffer
+ private long off; // current offset in temp file
+ private long prevend; // the end of the previous message in temp file
+ private MboxFolder.MessageMetadata md;
+ private byte[] buf = null;
+ // the length of the longest header we'll need to look at
+ private static final int LINELEN = "Content-Length: XXXXXXXXXX".length();
+ private char[] line;
+
+ public MessageLoader(TempFile temp) {
+ this.temp = temp;
+ }
+
+ /**
+ * Load messages from the given file descriptor, starting at the
+ * specified offset, adding the MessageMetadata to the list. <p>
+ *
+ * The data is assumed to be in UNIX mbox format, with newlines
+ * only as the line terminators.
+ */
+ public int load(FileDescriptor fd, long offset,
+ List<MboxFolder.MessageMetadata> msgs)
+ throws IOException {
+ // XXX - could allocate and deallocate buffers here
+ int loaded = 0;
+ try {
+ fis = new FileInputStream(fd);
+ if (fis.skip(offset) != offset)
+ throw new EOFException("Failed to skip to offset " + offset);
+ this.off = prevend = temp.length();
+ pos = len = 0;
+ line = new char[LINELEN];
+ buf = new byte[64 * 1024];
+ fos = temp.getAppendStream();
+ int n;
+ // keep loading messages as long as we have headers
+ while ((n = skipHeader(loaded == 0)) >= 0) {
+ long start;
+ if (n == 0) {
+ // didn't find a Content-Length, skip the body
+ start = skipBody();
+ if (start < 0) {
+ // md.end = -1;
+ md.dataend = -1;
+ msgs.add(md);
+ loaded++;
+ break;
+ }
+ md.dataend = start;
+ } else {
+ // skip over the body
+ skip(n);
+ md.dataend = off;
+ int b;
+ // skip any blank lines after the body
+ while ((b = get()) >= 0) {
+ if (b != '\n')
+ break;
+ }
+ start = off;
+ if (b >= 0)
+ start--; // back up one byte if not at EOF
+ }
+ // md.end = start;
+ prevend = start;
+ msgs.add(md);
+ loaded++;
+ }
+ } finally {
+ try {
+ fis.close();
+ } catch (IOException ex) {
+ // ignore
+ }
+ try {
+ fos.close();
+ } catch (IOException ex) {
+ // ignore
+ }
+ line = null;
+ buf = null;
+ }
+ return loaded;
+ }
+
+ /**
+ * Skip over the message header, returning the content length
+ * of the body, or 0 if no Content-Length header was seen.
+ * Update the MessageMetadata based on the headers seen.
+ * return -1 on EOF.
+ */
+ private int skipHeader(boolean first) throws IOException {
+ int clen = 0;
+ boolean bol = true;
+ int lpos = -1;
+ int b;
+ boolean saw_unix_from = false;
+ int lineno = 0;
+ md = new MboxFolder.MessageMetadata();
+ md.start = prevend;
+ md.recent = true;
+ while ((b = get()) >= 0) {
+ if (bol) {
+ if (b == '\n')
+ break;
+ lpos = 0;
+ }
+ if (b == '\n') {
+ bol = true;
+ lineno++;
+ // newline at end of line, was the line one of the headers
+ // we're looking for?
+ if (lpos > 7) {
+ // XXX - make this more efficient?
+ String s = new String(line, 0, lpos);
+ // fast check for Content-Length header
+ if (lineno == 1 && line[0] == 'F' && isPrefix(s, "From ")) {
+ saw_unix_from = true;
+ } else if (line[7] == '-' &&
+ isPrefix(s, "Content-Length:")) {
+ s = s.substring(15).trim();
+ try {
+ clen = Integer.parseInt(s);
+ } catch (NumberFormatException ex) {
+ // ignore it
+ }
+ // fast check for Status header
+ } else if ((line[1] == 't' || line[1] == 'T') &&
+ isPrefix(s, "Status:")) {
+ if (s.indexOf('O') >= 0)
+ md.recent = false;
+ // fast check for X-Status header
+ } else if ((line[3] == 't' || line[3] == 'T') &&
+ isPrefix(s, "X-Status:")) {
+ if (s.indexOf('D') >= 0)
+ md.deleted = true;
+ // fast check for X-Dt-Delete-Time header
+ } else if (line[4] == '-' &&
+ isPrefix(s, "X-Dt-Delete-Time:")) {
+ md.deleted = true;
+ // fast check for X-IMAP header
+ } else if (line[5] == 'P' && s.startsWith("X-IMAP:")) {
+ md.imap = true;
+ }
+ }
+ } else {
+ // accumlate data in line buffer
+ bol = false;
+ if (lpos < 0) // ignoring this line
+ continue;
+ if (lpos == 0 && (b == ' ' || b == '\t'))
+ lpos = -1; // ignore continuation lines
+ else if (lpos < line.length)
+ line[lpos++] = (char)b;
+ }
+ }
+
+ // if we hit EOF, or this is the first message we're loading and
+ // it doesn't have a UNIX From line, return EOF.
+ // (After the first message, UNIX From lines are seen by skipBody
+ // to terminate the message.)
+ if (b < 0 || (first && !saw_unix_from))
+ return -1;
+ else
+ return clen;
+ }
+
+ /**
+ * Does "s" start with "pre", ignoring case?
+ */
+ private static final boolean isPrefix(String s, String pre) {
+ return s.regionMatches(true, 0, pre, 0, pre.length());
+ }
+
+ /**
+ * Skip over the body of the message looking for a line that starts
+ * with "From ". If found, return the offset of the beginning of
+ * that line. Return -1 on EOF.
+ */
+ private long skipBody() throws IOException {
+ boolean bol = true;
+ int lpos = -1;
+ long loff = off;
+ int b;
+ while ((b = get()) >= 0) {
+ if (bol) {
+ lpos = 0;
+ loff = off - 1;
+ }
+ if (b == '\n') {
+ bol = true;
+ if (lpos >= 5) { // have enough data to test?
+ if (line[0] == 'F' && line[1] == 'r' && line[2] == 'o' &&
+ line[3] == 'm' && line[4] == ' ')
+ return loff;
+ }
+ } else {
+ bol = false;
+ if (lpos < 0)
+ continue;
+ if (lpos == 0 && b != 'F')
+ lpos = -1; // ignore lines that don't start with F
+ else if (lpos < 5) // only need first 5 chars to test
+ line[lpos++] = (char)b;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Skip "n" bytes, returning how much we were able to skip.
+ */
+ private final int skip(int n) throws IOException {
+ int n0 = n;
+ if (pos + n < len) {
+ pos += n; // can do it all within this buffer
+ off += n;
+ } else {
+ do {
+ n -= (len - pos); // skip rest of this buffer
+ off += (len - pos);
+ fill();
+ if (len <= 0) // ran out of data
+ return n0 - n;
+ } while (n > len);
+ pos += n;
+ off += n;
+ }
+ return n0;
+ }
+
+ /**
+ * Return the next byte.
+ */
+ private final int get() throws IOException {
+ if (pos >= len)
+ fill();
+ if (pos >= len)
+ return -1;
+ else {
+ off++;
+ return buf[pos++] & 0xff;
+ }
+ }
+
+ /**
+ * Fill our buffer with more data.
+ * Every buffer we read is also written to the temp file.
+ */
+ private final void fill() throws IOException {
+ len = fis.read(buf);
+ pos = 0;
+ if (len > 0)
+ fos.write(buf, 0, len);
+ }
+}