/* * 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); } }