/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (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.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "msgCore.h" // for pre-compiled headers #include "nsImapCore.h" #include "nsImapProtocol.h" #include "nsIMAPGenericParser.h" #include "nsString.h" #include "nsReadableUtils.h" /************************************************* The following functions are used to implement a thread safe strtok *************************************************/ /* * Get next token from string *stringp, where tokens are (possibly empty) * strings separated by characters from delim. Tokens are separated * by exactly one delimiter iff the skip parameter is false; otherwise * they are separated by runs of characters from delim, because we * skip over any initial `delim' characters. * * Writes NULs into the string at *stringp to end tokens. * delim will usually, but need not, remain CONSTant from call to call. * On return, *stringp points past the last NUL written (if there might * be further tokens), or is NULL (if there are definitely no more tokens). * * If *stringp is NULL, strtoken returns NULL. */ static char *strtoken_r(char ** stringp, const char *delim, int skip) { char *s; const char *spanp; int c, sc; char *tok; if ((s = *stringp) == NULL) return (NULL); if (skip) { /* * Skip (span) leading delimiters (s += strspn(s, delim)). */ cont: c = *s; for (spanp = delim; (sc = *spanp++) != 0;) { if (c == sc) { s++; goto cont; } } if (c == 0) { /* no token found */ *stringp = NULL; return (NULL); } } /* * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). * Note that delim must have one NUL; we stop if we see that, too. */ for (tok = s;;) { c = *s++; spanp = delim; do { if ((sc = *spanp++) == c) { if (c == 0) s = NULL; else s[-1] = 0; *stringp = s; return( (char *) tok ); } } while (sc != 0); } /* NOTREACHED */ return (NULL); } /* static */ char *nsIMAPGenericParser::Imapstrtok_r(char *s1, const char *s2, char **lasts) { if (s1) *lasts = s1; return (strtoken_r(lasts, s2, 1)); } ////////////////// nsIMAPGenericParser ///////////////////////// nsIMAPGenericParser::nsIMAPGenericParser() : fNextToken(nsnull), fCurrentLine(nsnull), fLineOfTokens(nsnull), fStartOfLineOfTokens(nsnull), fCurrentTokenPlaceHolder(nsnull), fAtEndOfLine(PR_FALSE), fTokenizerAdvanced(PR_FALSE), fSyntaxErrorLine(nsnull), fSyntaxError(PR_FALSE), fDisconnected(PR_FALSE) { } nsIMAPGenericParser::~nsIMAPGenericParser() { PR_FREEIF( fCurrentLine ); PR_FREEIF( fStartOfLineOfTokens); PR_FREEIF( fSyntaxErrorLine ); } void nsIMAPGenericParser::HandleMemoryFailure() { SetConnected(PR_FALSE); } void nsIMAPGenericParser::ResetLexAnalyzer() { PR_FREEIF( fCurrentLine ); PR_FREEIF( fStartOfLineOfTokens ); fTokenizerAdvanced = PR_FALSE; fCurrentLine = fNextToken = fLineOfTokens = fStartOfLineOfTokens = fCurrentTokenPlaceHolder = nsnull; fAtEndOfLine = PR_FALSE; } PRBool nsIMAPGenericParser::LastCommandSuccessful() { return Connected() && !SyntaxError(); } void nsIMAPGenericParser::SetSyntaxError(PRBool error) { fSyntaxError = error; PR_FREEIF( fSyntaxErrorLine ); if (error) { NS_ASSERTION(PR_FALSE, "syntax error in generic parser"); fSyntaxErrorLine = PL_strdup(fCurrentLine); } else fSyntaxErrorLine = NULL; } char *nsIMAPGenericParser::CreateSyntaxErrorLine() { return PL_strdup(fSyntaxErrorLine); } PRBool nsIMAPGenericParser::SyntaxError() { return fSyntaxError; } void nsIMAPGenericParser::SetConnected(PRBool connected) { fDisconnected = !connected; } PRBool nsIMAPGenericParser::Connected() { return !fDisconnected; } PRBool nsIMAPGenericParser::ContinueParse() { return !fSyntaxError && !fDisconnected; } PRBool nsIMAPGenericParser::at_end_of_line() { return (fAtEndOfLine || (nsCRT::strcmp(fNextToken, CRLF) == 0)); } void nsIMAPGenericParser::skip_to_CRLF() { while (Connected() && !at_end_of_line()) fNextToken = GetNextToken(); } // fNextToken initially should point to // a string after the initial open paren ("(") // After this call, fNextToken points to the // first character after the matching close // paren. Only call GetNextToken to get the NEXT // token after the one returned in fNextToken. void nsIMAPGenericParser::skip_to_close_paren() { int numberOfCloseParensNeeded = 1; if (fNextToken && *fNextToken == ')') { numberOfCloseParensNeeded--; fNextToken++; if (!fNextToken || !*fNextToken) fNextToken = GetNextToken(); } while (ContinueParse() && numberOfCloseParensNeeded > 0) { // go through fNextToken, count the number // of open and close parens, to account // for nested parens which might come in // the response char *loc = 0; for (loc = fNextToken; loc && *loc; loc++) { if (*loc == '(') numberOfCloseParensNeeded++; else if (*loc == ')') numberOfCloseParensNeeded--; if (numberOfCloseParensNeeded == 0) { fNextToken = loc + 1; if (!fNextToken || !*fNextToken) fNextToken = GetNextToken(); break; // exit the loop } } if (numberOfCloseParensNeeded > 0) fNextToken = GetNextToken(); } } char *nsIMAPGenericParser::GetNextToken() { if (!fCurrentLine || fAtEndOfLine) AdvanceToNextLine(); else if (Connected()) { if (fTokenizerAdvanced) { fNextToken = Imapstrtok_r(fLineOfTokens, WHITESPACE, &fCurrentTokenPlaceHolder); fTokenizerAdvanced = PR_FALSE; } else { fNextToken = Imapstrtok_r(nsnull, WHITESPACE, &fCurrentTokenPlaceHolder); } if (!fNextToken) { fAtEndOfLine = PR_TRUE; fNextToken = CRLF; } } return fNextToken; } void nsIMAPGenericParser::AdvanceToNextLine() { PR_FREEIF( fCurrentLine ); PR_FREEIF( fStartOfLineOfTokens); fTokenizerAdvanced = PR_FALSE; PRBool ok = GetNextLineForParser(&fCurrentLine); if (!ok) { SetConnected(PR_FALSE); fStartOfLineOfTokens = nsnull; fLineOfTokens = nsnull; fCurrentTokenPlaceHolder = nsnull; fNextToken = CRLF; } else if (fCurrentLine) // might be NULL if we are would_block ? { fStartOfLineOfTokens = PL_strdup(fCurrentLine); if (fStartOfLineOfTokens) { fLineOfTokens = fStartOfLineOfTokens; fNextToken = Imapstrtok_r(fLineOfTokens, WHITESPACE, &fCurrentTokenPlaceHolder); if (!fNextToken) { fAtEndOfLine = PR_TRUE; fNextToken = CRLF; } else fAtEndOfLine = PR_FALSE; } else HandleMemoryFailure(); } else HandleMemoryFailure(); } void nsIMAPGenericParser::AdvanceTokenizerStartingPoint(int32 bytesToAdvance) { PRInt32 startingDiff = fLineOfTokens - fStartOfLineOfTokens; PRInt32 nextTokenOffset; // save off offset into fStartOfLineOfTokens of fNextToken so we can set it appropriately // when we destroy the current line and create a new one. I'm pretty sure fNextToken must // point somewhere in the current line. nextTokenOffset = fNextToken - fStartOfLineOfTokens; PR_FREEIF(fStartOfLineOfTokens); if (fCurrentLine) { fStartOfLineOfTokens = PL_strdup(fCurrentLine); fNextToken = fStartOfLineOfTokens + nextTokenOffset; if (fStartOfLineOfTokens && ((int32) strlen(fStartOfLineOfTokens) >= bytesToAdvance)) { fLineOfTokens = fStartOfLineOfTokens + bytesToAdvance + startingDiff; fCurrentTokenPlaceHolder = fLineOfTokens; fTokenizerAdvanced = PR_TRUE; } else HandleMemoryFailure(); } else HandleMemoryFailure(); } // Lots of things in the IMAP protocol are defined as an "astring." // An astring is either an atom or a string. // An atom is just a series of one or more characters such as: hello // A string can either be quoted or literal. // Quoted: "Test Folder 1" // Literal: {13}Test Folder 1 // This function leaves us off with fCurrentTokenPlaceHolder immediately after // the end of the Astring. Call GetNextToken() to get the token after it. char *nsIMAPGenericParser::CreateAstring() { if (*fNextToken == '{') { return CreateLiteral(); // literal } else if (*fNextToken == '"') { return CreateQuoted(); // quoted } else { return CreateAtom(); // atom } } // Create an atom // This function does not advance the parser. // Call GetNextToken() to get the next token after the atom. char *nsIMAPGenericParser::CreateAtom() { char *rv = PL_strdup(fNextToken); //fNextToken = GetNextToken(); return (rv); } // CreateNilString creates either NIL (reutrns NULL) or a string // Call with fNextToken pointing to the thing which we think is the nilstring. // This function leaves us off with fCurrentTokenPlaceHolder immediately after // the end of the string, if it is a string, or at the NIL. // Regardless of type, call GetNextToken() to get the token after it. char *nsIMAPGenericParser::CreateNilString() { if (!PL_strncasecmp(fNextToken, "NIL", 3)) { if (strlen(fNextToken) != 3) fNextToken += 3; //fNextToken = GetNextToken(); return NULL; } else return CreateString(); } // Create a string, which can either be quoted or literal, // but not an atom. // This function leaves us off with fCurrentTokenPlaceHolder immediately after // the end of the String. Call GetNextToken() to get the token after it. char *nsIMAPGenericParser::CreateString() { if (*fNextToken == '{') { char *rv = CreateLiteral(); // literal return (rv); } else if (*fNextToken == '"') { char *rv = CreateQuoted(); // quoted //fNextToken = GetNextToken(); return (rv); } else { SetSyntaxError(PR_TRUE); return NULL; } } // This function leaves us off with fCurrentTokenPlaceHolder immediately after // the end of the closing quote. Call GetNextToken() to get the token after it. // Note that if the current line ends without the // closed quote then we have to fetch another line from the server, until // we find the close quote. char *nsIMAPGenericParser::CreateQuoted(PRBool /*skipToEnd*/) { char *currentChar = fCurrentLine + (fNextToken - fStartOfLineOfTokens) + 1; // one char past opening '"' int charIndex = 0; int escapeCharsCut = 0; PRBool closeQuoteFound = PR_FALSE; nsCString returnString(currentChar); while (!closeQuoteFound && ContinueParse()) { if (!returnString.CharAt(charIndex)) { AdvanceToNextLine(); returnString += fCurrentLine; charIndex++; } else if (returnString.CharAt(charIndex) == '"') { // don't check to see if it was escaped, // that was handled in the next clause closeQuoteFound = PR_TRUE; } else if (returnString.CharAt(charIndex) == '\\') { // eat the escape character returnString.Cut(charIndex, 1); // whatever the escaped character was, we want it charIndex++; // account for charIndex not reflecting the eat of the escape character escapeCharsCut++; } else charIndex++; } if (closeQuoteFound) { returnString.Truncate(charIndex); //if ((charIndex == 0) && skipToEnd) // it's an empty string. Why skip to end? // skip_to_CRLF(); //else if (charIndex == strlen(fCurrentLine)) // should we have this? //AdvanceToNextLine(); //else if (charIndex < (int) (strlen(fNextToken) - 2)) // -2 because of the start and end quotes { // the quoted string was fully contained within fNextToken, // and there is text after the quote in fNextToken that we // still need // int charDiff = strlen(fNextToken) - charIndex - 1; // fCurrentTokenPlaceHolder -= charDiff; // if (!nsCRT::strcmp(fCurrentTokenPlaceHolder, CRLF)) // fAtEndOfLine = PR_TRUE; AdvanceTokenizerStartingPoint ((fNextToken - fLineOfTokens) + returnString.Length() + escapeCharsCut + 2); if (!nsCRT::strcmp(fLineOfTokens, CRLF)) fAtEndOfLine = PR_TRUE; } else { fCurrentTokenPlaceHolder += escapeCharsCut + charIndex + 1 - strlen(fNextToken); if (!*fCurrentTokenPlaceHolder) *fCurrentTokenPlaceHolder = ' '; // put the token delimiter back /* if (!nsCRT::strcmp(fNextToken, CRLF)) fAtEndOfLine = PR_TRUE; */ } } else NS_ASSERTION(PR_FALSE, "didn't find close quote"); return ToNewCString(returnString); } // This function leaves us off with fCurrentTokenPlaceHolder immediately after // the end of the literal string. Call GetNextToken() to get the token after it // the literal string. char *nsIMAPGenericParser::CreateLiteral() { int32 numberOfCharsInMessage = atoi(fNextToken + 1); int32 charsReadSoFar = 0, currentLineLength = 0; int32 bytesToCopy = 0; uint32 numBytes = numberOfCharsInMessage + 1; NS_ASSERTION(numBytes, "overflow!"); if (!numBytes) return nsnull; char *returnString = (char *) PR_Malloc(numBytes); if (returnString) { *(returnString + numberOfCharsInMessage) = 0; // Null terminate it first PRBool terminatedLine = PR_FALSE; while (ContinueParse() && (charsReadSoFar < numberOfCharsInMessage)) { if (!terminatedLine) { if (fCurrentTokenPlaceHolder && *fCurrentTokenPlaceHolder == nsCRT::LF && *(fCurrentTokenPlaceHolder+1)) { // This is a static buffer, with a CRLF between the literal size ({91}) and // the string itself fCurrentTokenPlaceHolder++; } else { // We have to read the next line from AdvanceToNextLine(). terminatedLine = PR_TRUE; AdvanceToNextLine(); } } else AdvanceToNextLine(); if (ContinueParse()) { currentLineLength = strlen(terminatedLine ? fCurrentLine : fCurrentTokenPlaceHolder); bytesToCopy = (currentLineLength > numberOfCharsInMessage - charsReadSoFar ? numberOfCharsInMessage - charsReadSoFar : currentLineLength); NS_ASSERTION (bytesToCopy, "0 length literal?"); memcpy(returnString + charsReadSoFar, terminatedLine ? fCurrentLine : fCurrentTokenPlaceHolder, bytesToCopy); charsReadSoFar += bytesToCopy; } } if (ContinueParse()) { if (bytesToCopy == 0) { skip_to_CRLF(); fAtEndOfLine = PR_TRUE; } else if (currentLineLength == bytesToCopy) { fAtEndOfLine = PR_TRUE; } else { // Move fCurrentTokenPlaceHolder if (terminatedLine) AdvanceTokenizerStartingPoint (bytesToCopy); else AdvanceTokenizerStartingPoint ( bytesToCopy + strlen(fNextToken) + 2 /* CRLF */ + (fNextToken - fLineOfTokens) ); if (!*fCurrentTokenPlaceHolder) // landed on a token boundary fCurrentTokenPlaceHolder++; if (!nsCRT::strcmp(fCurrentTokenPlaceHolder, CRLF)) fAtEndOfLine = PR_TRUE; } } } return returnString; } // Call this to create a buffer containing all characters within // a given set of parentheses. // Call this with fNextToken[0]=='(', that is, the open paren // of the group. // It will allocate and return all characters up to and including the corresponding // closing paren, and leave the parser in the right place afterwards. char *nsIMAPGenericParser::CreateParenGroup() { #ifdef DEBUG_bienvenu NS_ASSERTION(fNextToken[0] == '(', "we don't have a paren group!"); #endif int numOpenParens = 1; // build up a buffer with the paren group. // start with an initial chunk, expand later if necessary nsCString buf; nsCString returnString; int bytesUsed = 0; // count the number of parens in the current token int count, tokenLen = strlen(fNextToken); for (count = 1; (count < tokenLen) && (numOpenParens > 0); count++) { if (fNextToken[count] == '(') numOpenParens++; else if (fNextToken[count] == ')') numOpenParens--; } if ((numOpenParens > 0) && ContinueParse()) { // Copy that first token from before returnString =fNextToken; returnString.Append(" "); // space that got stripped off the token PRBool extractReset = PR_TRUE; while (extractReset && ContinueParse()) { extractReset = PR_FALSE; // Go through the current line and look for the last close paren. // We're not trying to parse it just yet, just separate it out. int len = strlen(fCurrentTokenPlaceHolder); for (count = 0; (count < len) && (numOpenParens > 0) && !extractReset; count++) { if (*fCurrentTokenPlaceHolder == '{') { fNextToken = GetNextToken(); NS_ASSERTION(fNextToken, "out of memory?or invalid syntax"); if (fNextToken) { tokenLen = strlen(fNextToken); if (fNextToken[tokenLen-1] == '}') { // ok, we're looking at a literal string here // first, flush buf if (bytesUsed > 0) { buf.Truncate(bytesUsed); returnString.Append(buf); buf.Truncate(); bytesUsed = 0; } returnString.Append(fNextToken); // append the {xx} to the buffer returnString.Append(CRLF); // append a CRLF to the buffer char *lit = CreateLiteral(); fTokenizerAdvanced = PR_FALSE; // force it to use fCurrentTokenPlaceHolder NS_ASSERTION(lit, "syntax error or out of memory"); if (lit) { returnString.Append(lit); //fCurrentTokenPlaceHolder += nsCRT::strlen(lit); //AdvanceTokenizerStartingPoint(nsCRT::strlen(lit)); //fNextToken = GetNextToken(); extractReset = PR_TRUE; PR_Free(lit); } } else { #ifdef DEBUG_bienvenu NS_ASSERTION(PR_FALSE, "syntax error creating paren group"); // maybe not an error, but definitely a rare condition #endif } } } else if (*fCurrentTokenPlaceHolder == '"') { // We're looking at a quoted string here. // Ignore the characters within it. // first, flush buf if (bytesUsed > 0) { buf.Truncate(bytesUsed); returnString.Append(buf); buf.Truncate(); bytesUsed = 0; } fNextToken = GetNextToken(); NS_ASSERTION(fNextToken, "syntax error or out of memory creating paren group"); if (fNextToken) { char *q = CreateQuoted(); fTokenizerAdvanced = PR_FALSE; // force it to use fCurrentTokenPlaceHolder NS_ASSERTION(q, "syntax error or out of memory creating paren group"); if (q) { returnString.Append("\""); returnString.Append(q); returnString.Append("\""); extractReset = PR_TRUE; PR_Free(q); } } } else if (*fCurrentTokenPlaceHolder == '(') numOpenParens++; else if (*fCurrentTokenPlaceHolder == ')') numOpenParens--; if (!extractReset) { // append this character to the buffer buf += *fCurrentTokenPlaceHolder; //.SetCharAt(*fCurrentTokenPlaceHolder, bytesUsed); bytesUsed++; fCurrentTokenPlaceHolder++; } } } } else if ((numOpenParens == 0) && ContinueParse()) { // the whole paren group response was finished in a single token buf.Append(fNextToken); } if (numOpenParens != 0 || !ContinueParse()) { SetSyntaxError(PR_TRUE); returnString.SetLength(0); } else { // flush buf the final time if (bytesUsed > 0) { buf.Truncate(bytesUsed); returnString.Append(buf); buf.Truncate(); } fNextToken = GetNextToken(); } return ToNewCString(returnString); }