/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
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 AppendStream fos = null;
private int pos, len;
private long off;
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 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 = offset;
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()) >= 0) {
long start;
if (n == 0) {
// didn't find a Content-Length, skip the body
start = skipBody();
if (start < 0) {
md.end = -1;
msgs.add(md);
loaded++;
break;
}
} else {
// skip over the body
skip(n);
int b;
start = off;
// skip any blank lines after the body
while ((b = get()) >= 0) {
if (b != '\n')
break;
}
}
md.end = 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() throws IOException {
int clen = 0;
boolean bol = true;
int lpos = -1;
int b;
md = new MboxFolder.MessageMetadata();
md.recent = true;
while ((b = get()) >= 0) {
if (bol) {
if (b == '\n')
break;
lpos = 0;
}
if (b == '\n') {
bol = true;
// 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 (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 (b < 0)
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);
}
}