diff options
Diffstat (limited to 'src/com/android/email/mail/store/imap/ImapResponseParser.java')
-rw-r--r-- | src/com/android/email/mail/store/imap/ImapResponseParser.java | 453 |
1 files changed, 0 insertions, 453 deletions
diff --git a/src/com/android/email/mail/store/imap/ImapResponseParser.java b/src/com/android/email/mail/store/imap/ImapResponseParser.java deleted file mode 100644 index 8dd1cf610..000000000 --- a/src/com/android/email/mail/store/imap/ImapResponseParser.java +++ /dev/null @@ -1,453 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.email.mail.store.imap; - -import android.text.TextUtils; - -import com.android.email.DebugUtils; -import com.android.email.FixedLengthInputStream; -import com.android.email.PeekableInputStream; -import com.android.email.mail.transport.DiscourseLogger; -import com.android.emailcommon.Logging; -import com.android.emailcommon.mail.MessagingException; -import com.android.emailcommon.utility.LoggingInputStream; -import com.android.mail.utils.LogUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - -/** - * IMAP response parser. - */ -public class ImapResponseParser { - private static final boolean DEBUG_LOG_RAW_STREAM = false; // DO NOT RELEASE AS 'TRUE' - - /** - * Literal larger than this will be stored in temp file. - */ - public static final int LITERAL_KEEP_IN_MEMORY_THRESHOLD = 2 * 1024 * 1024; - - /** Input stream */ - private final PeekableInputStream mIn; - - /** - * To log network activities when the parser crashes. - * - * <p>We log all bytes received from the server, except for the part sent as literals. - */ - private final DiscourseLogger mDiscourseLogger; - - private final int mLiteralKeepInMemoryThreshold; - - /** StringBuilder used by readUntil() */ - private final StringBuilder mBufferReadUntil = new StringBuilder(); - - /** StringBuilder used by parseBareString() */ - private final StringBuilder mParseBareString = new StringBuilder(); - - /** - * We store all {@link ImapResponse} in it. {@link #destroyResponses()} must be called from - * time to time to destroy them and clear it. - */ - private final ArrayList<ImapResponse> mResponsesToDestroy = new ArrayList<ImapResponse>(); - - /** - * Exception thrown when we receive BYE. It derives from IOException, so it'll be treated - * in the same way EOF does. - */ - public static class ByeException extends IOException { - public static final String MESSAGE = "Received BYE"; - public ByeException() { - super(MESSAGE); - } - } - - /** - * Public constructor for normal use. - */ - public ImapResponseParser(InputStream in, DiscourseLogger discourseLogger) { - this(in, discourseLogger, LITERAL_KEEP_IN_MEMORY_THRESHOLD); - } - - /** - * Constructor for testing to override the literal size threshold. - */ - /* package for test */ ImapResponseParser(InputStream in, DiscourseLogger discourseLogger, - int literalKeepInMemoryThreshold) { - if (DEBUG_LOG_RAW_STREAM && DebugUtils.DEBUG) { - in = new LoggingInputStream(in); - } - mIn = new PeekableInputStream(in); - mDiscourseLogger = discourseLogger; - mLiteralKeepInMemoryThreshold = literalKeepInMemoryThreshold; - } - - private static IOException newEOSException() { - final String message = "End of stream reached"; - if (DebugUtils.DEBUG) { - LogUtils.d(Logging.LOG_TAG, message); - } - return new IOException(message); - } - - /** - * Peek next one byte. - * - * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n, - * we shouldn't see EOF during parsing. - */ - private int peek() throws IOException { - final int next = mIn.peek(); - if (next == -1) { - throw newEOSException(); - } - return next; - } - - /** - * Read and return one byte from {@link #mIn}, and put it in {@link #mDiscourseLogger}. - * - * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n, - * we shouldn't see EOF during parsing. - */ - private int readByte() throws IOException { - int next = mIn.read(); - if (next == -1) { - throw newEOSException(); - } - mDiscourseLogger.addReceivedByte(next); - return next; - } - - /** - * Destroy all the {@link ImapResponse}s stored in the internal storage and clear it. - * - * @see #readResponse() - */ - public void destroyResponses() { - for (ImapResponse r : mResponsesToDestroy) { - r.destroy(); - } - mResponsesToDestroy.clear(); - } - - /** - * Reads the next response available on the stream and returns an - * {@link ImapResponse} object that represents it. - * - * <p>When this method successfully returns an {@link ImapResponse}, the {@link ImapResponse} - * is stored in the internal storage. When the {@link ImapResponse} is no longer used - * {@link #destroyResponses} should be called to destroy all the responses in the array. - * - * @return the parsed {@link ImapResponse} object. - * @exception ByeException when detects BYE. - */ - public ImapResponse readResponse() throws IOException, MessagingException { - ImapResponse response = null; - try { - response = parseResponse(); - if (DebugUtils.DEBUG) { - LogUtils.d(Logging.LOG_TAG, "<<< " + response.toString()); - } - - } catch (RuntimeException e) { - // Parser crash -- log network activities. - onParseError(e); - throw e; - } catch (IOException e) { - // Network error, or received an unexpected char. - onParseError(e); - throw e; - } - - // Handle this outside of try-catch. We don't have to dump protocol log when getting BYE. - if (response.is(0, ImapConstants.BYE)) { - LogUtils.w(Logging.LOG_TAG, ByeException.MESSAGE); - response.destroy(); - throw new ByeException(); - } - mResponsesToDestroy.add(response); - return response; - } - - private void onParseError(Exception e) { - // Read a few more bytes, so that the log will contain some more context, even if the parser - // crashes in the middle of a response. - // This also makes sure the byte in question will be logged, no matter where it crashes. - // e.g. when parseAtom() peeks and finds at an unexpected char, it throws an exception - // before actually reading it. - // However, we don't want to read too much, because then it may get into an email message. - try { - for (int i = 0; i < 4; i++) { - int b = readByte(); - if (b == -1 || b == '\n') { - break; - } - } - } catch (IOException ignore) { - } - LogUtils.w(Logging.LOG_TAG, "Exception detected: " + e.getMessage()); - mDiscourseLogger.logLastDiscourse(); - } - - /** - * Read next byte from stream and throw it away. If the byte is different from {@code expected} - * throw {@link MessagingException}. - */ - /* package for test */ void expect(char expected) throws IOException { - final int next = readByte(); - if (expected != next) { - throw new IOException(String.format("Expected %04x (%c) but got %04x (%c)", - (int) expected, expected, next, (char) next)); - } - } - - /** - * Read bytes until we find {@code end}, and return all as string. - * The {@code end} will be read (rather than peeked) and won't be included in the result. - */ - /* package for test */ String readUntil(char end) throws IOException { - mBufferReadUntil.setLength(0); - for (;;) { - final int ch = readByte(); - if (ch != end) { - mBufferReadUntil.append((char) ch); - } else { - return mBufferReadUntil.toString(); - } - } - } - - /** - * Read all bytes until \r\n. - */ - /* package */ String readUntilEol() throws IOException { - String ret = readUntil('\r'); - expect('\n'); // TODO Should this really be error? - return ret; - } - - /** - * Parse and return the response line. - */ - private ImapResponse parseResponse() throws IOException, MessagingException { - // We need to destroy the response if we get an exception. - // So, we first store the response that's being built in responseToDestroy, until it's - // completely built, at which point we copy it into responseToReturn and null out - // responseToDestroyt. - // If responseToDestroy is not null in finally, we destroy it because that means - // we got an exception somewhere. - ImapResponse responseToDestroy = null; - final ImapResponse responseToReturn; - - try { - final int ch = peek(); - if (ch == '+') { // Continuation request - readByte(); // skip + - expect(' '); - responseToDestroy = new ImapResponse(null, true); - - // If it's continuation request, we don't really care what's in it. - responseToDestroy.add(new ImapSimpleString(readUntilEol())); - - // Response has successfully been built. Let's return it. - responseToReturn = responseToDestroy; - responseToDestroy = null; - } else { - // Status response or response data - final String tag; - if (ch == '*') { - tag = null; - readByte(); // skip * - expect(' '); - } else { - tag = readUntil(' '); - } - responseToDestroy = new ImapResponse(tag, false); - - final ImapString firstString = parseBareString(); - responseToDestroy.add(firstString); - - // parseBareString won't eat a space after the string, so we need to skip it, - // if exists. - // If the next char is not ' ', it should be EOL. - if (peek() == ' ') { - readByte(); // skip ' ' - - if (responseToDestroy.isStatusResponse()) { // It's a status response - - // Is there a response code? - final int next = peek(); - if (next == '[') { - responseToDestroy.add(parseList('[', ']')); - if (peek() == ' ') { // Skip following space - readByte(); - } - } - - String rest = readUntilEol(); - if (!TextUtils.isEmpty(rest)) { - // The rest is free-form text. - responseToDestroy.add(new ImapSimpleString(rest)); - } - } else { // It's a response data. - parseElements(responseToDestroy, '\0'); - } - } else { - expect('\r'); - expect('\n'); - } - - // Response has successfully been built. Let's return it. - responseToReturn = responseToDestroy; - responseToDestroy = null; - } - } finally { - if (responseToDestroy != null) { - // We get an exception. - responseToDestroy.destroy(); - } - } - - return responseToReturn; - } - - private ImapElement parseElement() throws IOException, MessagingException { - final int next = peek(); - switch (next) { - case '(': - return parseList('(', ')'); - case '[': - return parseList('[', ']'); - case '"': - readByte(); // Skip " - return new ImapSimpleString(readUntil('"')); - case '{': - return parseLiteral(); - case '\r': // CR - readByte(); // Consume \r - expect('\n'); // Should be followed by LF. - return null; - case '\n': // LF // There shouldn't be a bare LF, but just in case. - readByte(); // Consume \n - return null; - default: - return parseBareString(); - } - } - - /** - * Parses an atom. - * - * Special case: If an atom contains '[', everything until the next ']' will be considered - * a part of the atom. - * (e.g. "BODY[HEADER.FIELDS ("DATE" ...)]" will become a single ImapString) - * - * If the value is "NIL", returns an empty string. - */ - private ImapString parseBareString() throws IOException, MessagingException { - mParseBareString.setLength(0); - for (;;) { - final int ch = peek(); - - // TODO Can we clean this up? (This condition is from the old parser.) - if (ch == '(' || ch == ')' || ch == '{' || ch == ' ' || - // ']' is not part of atom (it's in resp-specials) - ch == ']' || - // docs claim that flags are \ atom but atom isn't supposed to - // contain - // * and some flags contain * - // ch == '%' || ch == '*' || - ch == '%' || - // TODO probably should not allow \ and should recognize - // it as a flag instead - // ch == '"' || ch == '\' || - ch == '"' || (0x00 <= ch && ch <= 0x1f) || ch == 0x7f) { - if (mParseBareString.length() == 0) { - throw new MessagingException("Expected string, none found."); - } - String s = mParseBareString.toString(); - - // NIL will be always converted into the empty string. - if (ImapConstants.NIL.equalsIgnoreCase(s)) { - return ImapString.EMPTY; - } - return new ImapSimpleString(s); - } else if (ch == '[') { - // Eat all until next ']' - mParseBareString.append((char) readByte()); - mParseBareString.append(readUntil(']')); - mParseBareString.append(']'); // readUntil won't include the end char. - } else { - mParseBareString.append((char) readByte()); - } - } - } - - private void parseElements(ImapList list, char end) - throws IOException, MessagingException { - for (;;) { - for (;;) { - final int next = peek(); - if (next == end) { - return; - } - if (next != ' ') { - break; - } - // Skip space - readByte(); - } - final ImapElement el = parseElement(); - if (el == null) { // EOL - return; - } - list.add(el); - } - } - - private ImapList parseList(char opening, char closing) - throws IOException, MessagingException { - expect(opening); - final ImapList list = new ImapList(); - parseElements(list, closing); - expect(closing); - return list; - } - - private ImapString parseLiteral() throws IOException, MessagingException { - expect('{'); - final int size; - try { - size = Integer.parseInt(readUntil('}')); - } catch (NumberFormatException nfe) { - throw new MessagingException("Invalid length in literal"); - } - if (size < 0) { - throw new MessagingException("Invalid negative length in literal"); - } - expect('\r'); - expect('\n'); - FixedLengthInputStream in = new FixedLengthInputStream(mIn, size); - if (size > mLiteralKeepInMemoryThreshold) { - return new ImapTempFileLiteral(in); - } else { - return new ImapMemoryLiteral(in); - } - } -} |