qtcontactsmobility/src/versit/qversitreader_p.cpp
changeset 25 76a2435edfd4
parent 24 0ba2181d7c28
child 27 de1630741fbe
equal deleted inserted replaced
24:0ba2181d7c28 25:76a2435edfd4
    38 ** $QT_END_LICENSE$
    38 ** $QT_END_LICENSE$
    39 **
    39 **
    40 ****************************************************************************/
    40 ****************************************************************************/
    41 
    41 
    42 #include "qversitreader_p.h"
    42 #include "qversitreader_p.h"
       
    43 #include "qversitdocument.h"
    43 #include "versitutils_p.h"
    44 #include "versitutils_p.h"
    44 #include "qmobilityglobal.h"
    45 #include "qmobilityglobal.h"
    45 
    46 #include <QTextCodec>
    46 QTM_BEGIN_NAMESPACE
    47 #include <QMutexLocker>
    47 
    48 #include <QVariant>
    48 // Some big enough value for nested versit documents to prevent infite recursion
    49 #include <QBuffer>
       
    50 
       
    51 QTM_USE_NAMESPACE
       
    52 
       
    53 // Some big enough value for nested versit documents to prevent infinite recursion
    49 #define MAX_VERSIT_DOCUMENT_NESTING_DEPTH 20
    54 #define MAX_VERSIT_DOCUMENT_NESTING_DEPTH 20
       
    55 
       
    56 /*!
       
    57   \class LineReader
       
    58   \brief The LineReader class is a wrapper around a QIODevice that allows line-by-line reading.
       
    59 
       
    60   This class keeps an internal buffer which it uses to temporarily store data which it has read from
       
    61   the device but not returned to the user.
       
    62  */
       
    63 
       
    64 /*!
       
    65   Constructs a LineReader that reads from the given \a device using the given \a codec.
       
    66   \a chunkSize is the number of bytes to read at a time (it is useful for testing but shouldn't
       
    67   otherwise be set).
       
    68   */
       
    69 LineReader::LineReader(QIODevice* device, QTextCodec *codec, int chunkSize)
       
    70     : mDevice(device),
       
    71     mCodec(codec),
       
    72     mChunkSize(chunkSize),
       
    73     mCrlfList(*VersitUtils::newlineList(mCodec)),
       
    74     mBuffer(VersitCursor(QByteArray())),
       
    75     mOdometer(0)
       
    76 {
       
    77 }
       
    78 
       
    79 /*!
       
    80   Attempts to read a line and returns a VersitCursor describing the line.  The cursor returned
       
    81   includes the data, as well as the position and selection index bounds.  Data within those bounds
       
    82   represents the line.  Data outside those bounds should not be used.
       
    83  */
       
    84 VersitCursor LineReader::readLine()
       
    85 {
       
    86     mBuffer.position = mBuffer.selection;
       
    87     mSearchFrom = mBuffer.position;
       
    88 
       
    89     // First, look for a newline in the already-existing buffer.  If found, return the line.
       
    90     if (tryReadLine(mBuffer, false)) {
       
    91         mBuffer.dropOldData();
       
    92         mOdometer += mBuffer.selection - mBuffer.position;
       
    93         return mBuffer;
       
    94     }
       
    95 
       
    96     // Otherwise, keep reading more data until either a CRLF is found, or there's no more to read.
       
    97     while (!mDevice->atEnd()) {
       
    98         QByteArray temp = mDevice->read(mChunkSize);
       
    99         mBuffer.data.append(temp);
       
   100         if (tryReadLine(mBuffer, false)) {
       
   101             mBuffer.dropOldData();
       
   102             mOdometer += mBuffer.selection - mBuffer.position;
       
   103             return mBuffer;
       
   104         }
       
   105     }
       
   106 
       
   107     // We've reached the end of the stream.  Find a newline from the buffer (or return what's left).
       
   108     tryReadLine(mBuffer, true);
       
   109     mBuffer.dropOldData();
       
   110     mOdometer += mBuffer.selection - mBuffer.position;
       
   111     return mBuffer;
       
   112 }
       
   113 
       
   114 /*!
       
   115   How many bytes have been returned in the VersitCursor in the lifetime of the LineReader.
       
   116  */
       
   117 int LineReader::odometer()
       
   118 {
       
   119     return mOdometer;
       
   120 }
       
   121 
       
   122 /*!
       
   123   Returns true if there are no more lines left for readLine() to return.  It is possible for atEnd()
       
   124   to return false and for there to be no more data left (eg. if there are trailing newlines at the
       
   125   end of the input.  In this case, readLine() will return an empty line (ie. position == selection).
       
   126  */
       
   127 bool LineReader::atEnd()
       
   128 {
       
   129     return mDevice->atEnd() && mBuffer.selection == mBuffer.data.size();
       
   130 }
       
   131 
       
   132 /*!
       
   133   Returns the codec that the LineReader reads with.
       
   134  */
       
   135 QTextCodec* LineReader::codec()
       
   136 {
       
   137     return mCodec;
       
   138 }
       
   139 
       
   140 /*!
       
   141  * Get the next line of input from \a device to parse.  Also performs unfolding by removing
       
   142  * sequences of newline-space from the retrieved line.  Skips over any newlines at the start of the
       
   143  * input.
       
   144  *
       
   145  * Returns a VersitCursor containing and selecting the line.
       
   146  */
       
   147 bool LineReader::tryReadLine(VersitCursor &cursor, bool atEnd)
       
   148 {
       
   149     int crlfPos;
       
   150 
       
   151     QByteArray space = VersitUtils::encode(' ', mCodec);
       
   152     QByteArray tab = VersitUtils::encode('\t', mCodec);
       
   153     int spaceLength = space.length();
       
   154 
       
   155     forever {
       
   156         foreach(const QByteArrayMatcher& crlf, mCrlfList) {
       
   157             int crlfLength = crlf.pattern().length();
       
   158             crlfPos = crlf.indexIn(cursor.data, mSearchFrom);
       
   159             if (crlfPos == cursor.position) {
       
   160                 // Newline at start of line.  Set position to directly after it.
       
   161                 cursor.position += crlfLength;
       
   162                 mSearchFrom = cursor.position;
       
   163                 break;
       
   164             } else if (crlfPos > cursor.position) {
       
   165                 // Found the CRLF.
       
   166                 if (QVersitReaderPrivate::containsAt(cursor.data, space, crlfPos + crlfLength)
       
   167                     || QVersitReaderPrivate::containsAt(cursor.data, tab, crlfPos + crlfLength)) {
       
   168                     // If it's followed by whitespace, collapse it.
       
   169                     cursor.data.remove(crlfPos, crlfLength + spaceLength);
       
   170                     mSearchFrom = crlfPos;
       
   171                     break;
       
   172                 } else if (!atEnd && crlfPos + crlfLength + spaceLength >= cursor.data.size()) {
       
   173                     // If our CRLF is at the end of the current buffer but there's more to read,
       
   174                     // it's possible that a space could be hiding on the next read from the device.
       
   175                     // Just pretend we didn't see the CRLF and pick it up the next time round.
       
   176                     mSearchFrom = crlfPos;
       
   177                     return false;
       
   178                 } else {
       
   179                     // Found the CRLF.
       
   180                     cursor.selection = crlfPos;
       
   181                     return true;
       
   182                 }
       
   183             }
       
   184         }
       
   185         if (crlfPos == -1) {
       
   186             // No CRLF found.
       
   187             cursor.selection = cursor.data.size();
       
   188             return false;
       
   189         }
       
   190     }
       
   191 }
    50 
   192 
    51 /*! Construct a reader. */
   193 /*! Construct a reader. */
    52 QVersitReaderPrivate::QVersitReaderPrivate()
   194 QVersitReaderPrivate::QVersitReaderPrivate()
    53     : mIoDevice(0),
   195     : mIoDevice(0),
    54     mDocumentNestingLevel(0)
   196     mDocumentNestingLevel(0),
    55 {
   197     mDefaultCodec(QTextCodec::codecForName("UTF-8")),
    56 }
   198     mState(QVersitReader::InactiveState),
    57     
   199     mError(QVersitReader::NoError),
    58 /*! Destroy a reader. */    
   200     mIsCanceling(false)
       
   201 {
       
   202 }
       
   203 
       
   204 /*! Destroy a reader. */
    59 QVersitReaderPrivate::~QVersitReaderPrivate()
   205 QVersitReaderPrivate::~QVersitReaderPrivate()
    60 {
   206 {
    61 }
   207 }
    62 
   208 
    63 /*!
   209 /*!
    64  * Checks whether the reader is ready for reading.
   210  * Inherited from QThread, called by QThread when the thread has been started.
    65  */
   211  */
    66 bool QVersitReaderPrivate::isReady() const
   212 void QVersitReaderPrivate::run()
    67 {
   213 {
    68     return (mIoDevice && mIoDevice->isOpen());
   214     read();
    69 }
   215 }
    70 
   216 
    71 /*!
   217 /*!
    72  * Inherited from QThread. Does the actual reading.
   218  * Does the actual reading and sets the error and state as appropriate.
    73  */
   219  * If \a async, then stateChanged() signals are emitted as the reading happens.
    74 bool QVersitReaderPrivate::read()
   220  */
    75 {
   221 void QVersitReaderPrivate::read()
       
   222 {
       
   223     mMutex.lock();
    76     mVersitDocuments.clear();
   224     mVersitDocuments.clear();
    77     if (isReady()) {
   225     mMutex.unlock();
    78         QByteArray input = mIoDevice->readAll();
   226     bool canceled = false;
    79         VersitUtils::unfold(input);
   227 
    80         while (input.length() > 0) {
   228     LineReader lineReader(mIoDevice, mDefaultCodec);
    81             QVersitDocument document;
   229     while(!lineReader.atEnd()) {
    82             if (parseVersitDocument(input,document) &&
   230         if (isCanceling()) {
    83                 document.properties().count() > 0)
   231             canceled = true;
       
   232             break;
       
   233         }
       
   234         QVersitDocument document;
       
   235         int oldPos = lineReader.odometer();
       
   236         bool ok = parseVersitDocument(lineReader, document);
       
   237 
       
   238         if (ok) {
       
   239             if (document.isEmpty())
       
   240                 break;
       
   241             else {
       
   242                 QMutexLocker locker(&mMutex);
    84                 mVersitDocuments.append(document);
   243                 mVersitDocuments.append(document);
    85         }
   244                 emit resultsAvailable(mVersitDocuments);
    86     }
   245             }
    87 
   246         } else {
    88     return (mVersitDocuments.count() > 0);
   247             setError(QVersitReader::ParseError);
    89 }
   248             if (lineReader.odometer() == oldPos)
    90 
   249                 break;
    91 /*!
   250         }
    92  * Inherited from QThread, called by QThread when the thread has been started.
   251     };
    93  */
   252     if (canceled)
    94 void QVersitReaderPrivate::run()
   253         setState(QVersitReader::CanceledState);
    95 {
   254     else
    96     read();
   255         setState(QVersitReader::FinishedState);
       
   256 }
       
   257 
       
   258 void QVersitReaderPrivate::setState(QVersitReader::State state)
       
   259 {
       
   260     mMutex.lock();
       
   261     mState = state;
       
   262     mMutex.unlock();
       
   263     emit stateChanged(state);
       
   264 }
       
   265 
       
   266 QVersitReader::State QVersitReaderPrivate::state() const
       
   267 {
       
   268     QMutexLocker locker(&mMutex);
       
   269     return mState;
       
   270 }
       
   271 
       
   272 void QVersitReaderPrivate::setError(QVersitReader::Error error)
       
   273 {
       
   274     QMutexLocker locker(&mMutex);
       
   275     mError = error;
       
   276 }
       
   277 
       
   278 QVersitReader::Error QVersitReaderPrivate::error() const
       
   279 {
       
   280     QMutexLocker locker(&mMutex);
       
   281     return mError;
       
   282 }
       
   283 
       
   284 void QVersitReaderPrivate::setCanceling(bool canceling)
       
   285 {
       
   286     QMutexLocker locker(&mMutex);
       
   287     mIsCanceling = canceling;
       
   288 }
       
   289 
       
   290 bool QVersitReaderPrivate::isCanceling()
       
   291 {
       
   292     QMutexLocker locker(&mMutex);
       
   293     return mIsCanceling;
    97 }
   294 }
    98 
   295 
    99 /*!
   296 /*!
   100  * Parses a versit document. Returns true if the parsing was successful.
   297  * Parses a versit document. Returns true if the parsing was successful.
   101  */
   298  */
   102 bool QVersitReaderPrivate::parseVersitDocument(
   299 bool QVersitReaderPrivate::parseVersitDocument(LineReader& lineReader, QVersitDocument& document,
   103     QByteArray& text,
   300                                                bool foundBegin)
   104     QVersitDocument& document)
       
   105 {
   301 {
   106     if (mDocumentNestingLevel >= MAX_VERSIT_DOCUMENT_NESTING_DEPTH)
   302     if (mDocumentNestingLevel >= MAX_VERSIT_DOCUMENT_NESTING_DEPTH)
   107         return false; // To prevent infinite recursion
   303         return false; // To prevent infinite recursion
       
   304 
   108     bool parsingOk = true;
   305     bool parsingOk = true;
   109     mDocumentNestingLevel++;
   306     mDocumentNestingLevel++;
   110     text = text.mid(VersitUtils::countLeadingWhiteSpaces(text));
   307 
   111     QVersitProperty property =
   308     // TODO: Various readers should be made subclasses and eliminate assumptions like this.
   112         parseNextVersitProperty(document.versitType(),text);
   309     // We don't know what type it is: just assume it's a vCard 3.0
   113     if (property.name() == QString::fromAscii("BEGIN") && 
   310     document.setType(QVersitDocument::VCard30Type);
   114         property.value().trimmed().toUpper() == "VCARD") {
   311 
   115         while (property.name().length() > 0 &&
   312     QVersitProperty property;
   116                property.name() != QString::fromAscii("END")) {
   313 
   117             property = parseNextVersitProperty(document.versitType(),text);
   314     if (!foundBegin) {
   118             if (property.name() == QString::fromAscii("BEGIN") &&
   315         property = parseNextVersitProperty(document.type(), lineReader);
   119                 property.value().trimmed().toUpper() == "VCARD") {
   316         if (property.name() == QLatin1String("BEGIN")
       
   317             && property.value().trimmed().toUpper() == QLatin1String("VCARD")) {
       
   318             foundBegin = true;
       
   319         } else if (property.isEmpty()) {
       
   320             // A blank document (or end of file) was found.
       
   321             document = QVersitDocument();
       
   322         } else {
       
   323             // Some property other than BEGIN was found.
       
   324             parsingOk = false;
       
   325         }
       
   326     }
       
   327 
       
   328     if (foundBegin) {
       
   329         do {
       
   330             /* Grab it */
       
   331             property = parseNextVersitProperty(document.type(), lineReader);
       
   332 
       
   333             /* Discard embedded vcard documents - not supported yet.  Discard the entire vCard */
       
   334             if (property.name() == QLatin1String("BEGIN") &&
       
   335                 QString::compare(property.value().trimmed(),
       
   336                                  QLatin1String("VCARD"), Qt::CaseInsensitive) == 0) {
   120                 parsingOk = false;
   337                 parsingOk = false;
   121                 // Parse through the embedded cards, but don't save them
       
   122                 text.prepend("BEGIN:VCARD\r\n");
       
   123                 QVersitDocument nestedDocument;
   338                 QVersitDocument nestedDocument;
   124                 if (!parseVersitDocument(text,nestedDocument))
   339                 if (!parseVersitDocument(lineReader, nestedDocument, true))
   125                     break;
   340                     break;
   126             }
   341             }
   127             if (!setVersionFromProperty(document,property)) {
   342 
       
   343             // See if this is a version property and continue parsing under that version
       
   344             if (!setVersionFromProperty(document, property)) {
   128                 parsingOk = false;
   345                 parsingOk = false;
   129                 break;
   346                 break;
   130             }
   347             }
   131             if (property.name() != QString::fromAscii("VERSION") && 
   348 
   132                 property.name() != QString::fromAscii("END"))
   349             /* Nope, something else.. just add it */
       
   350             if (property.name() != QLatin1String("VERSION") &&
       
   351                 property.name() != QLatin1String("END"))
   133                 document.addProperty(property);
   352                 document.addProperty(property);
   134 
   353         } while (property.name().length() > 0 && property.name() != QLatin1String("END"));
   135         }
   354         if (property.name() != QLatin1String("END"))
       
   355             parsingOk = false;
   136     }
   356     }
   137     mDocumentNestingLevel--;
   357     mDocumentNestingLevel--;
   138     if (!parsingOk)
   358     if (!parsingOk)
   139         document = QVersitDocument();
   359         document = QVersitDocument();
       
   360 
   140     return parsingOk;
   361     return parsingOk;
   141 }
   362 }
   142 
   363 
   143 /*!
   364 /*!
   144  * Parses a versit document and returns whether parsing succeeded.
   365  * Parses a versit document and returns whether parsing succeeded.
   145  */
   366  */
   146 QVersitProperty QVersitReaderPrivate::parseNextVersitProperty(
   367 QVersitProperty QVersitReaderPrivate::parseNextVersitProperty(
   147     QVersitDocument::VersitType versitType,
   368         QVersitDocument::VersitType versitType,
   148     QByteArray& text)
   369         LineReader& lineReader)
   149 {
   370 {
       
   371     VersitCursor cursor = lineReader.readLine();
       
   372     if (cursor.position >= cursor.selection)
       
   373         return QVersitProperty();
       
   374 
       
   375     // Otherwise, do stuff.
       
   376     QPair<QStringList,QString> groupsAndName =
       
   377             extractPropertyGroupsAndName(cursor, lineReader.codec());
       
   378 
   150     QVersitProperty property;
   379     QVersitProperty property;
   151     QPair<QStringList,QString> groupsAndName =
       
   152         VersitUtils::extractPropertyGroupsAndName(text);
       
   153     property.setGroups(groupsAndName.first);
   380     property.setGroups(groupsAndName.first);
   154     property.setName(groupsAndName.second);
   381     property.setName(groupsAndName.second);
   155     if (versitType == QVersitDocument::VCard21)
   382 
   156         parseVCard21Property(text,property);
   383     if (versitType == QVersitDocument::VCard21Type)
   157     else if (versitType == QVersitDocument::VCard30)
   384         parseVCard21Property(cursor, property, lineReader);
   158         parseVCard30Property(text,property);
   385     else if (versitType == QVersitDocument::VCard30Type)
   159     else
   386         parseVCard30Property(cursor, property, lineReader);
   160         return QVersitProperty(); // type not supported
   387 
   161     return property;
   388     return property;
   162 }
   389 }
   163 
   390 
   164 /*!
   391 /*!
   165  * Parses the property according to vCard 2.1 syntax.
   392  * Parses the property according to vCard 2.1 syntax.
   166  */
   393  */
   167 void QVersitReaderPrivate::parseVCard21Property(
   394 void QVersitReaderPrivate::parseVCard21Property(VersitCursor& cursor, QVersitProperty& property,
   168     QByteArray& text,
   395                                                 LineReader& lineReader)
   169     QVersitProperty& property)
   396 {
   170 {
   397     property.setParameters(extractVCard21PropertyParams(cursor, lineReader.codec()));
   171     property.setParameters(VersitUtils::extractVCard21PropertyParams(text));
   398 
   172     text = VersitUtils::extractPropertyValue(text);
   399     QByteArray value = extractPropertyValue(cursor);
   173     if (property.name() == QString::fromAscii("AGENT")) {
   400     if (property.name() == QLatin1String("AGENT")) {
   174         parseAgentProperty(text,property);
   401         // Hack to handle cases where start of document is on the same or next line as "AGENT:"
       
   402         // XXX: Handle non-ASCII charsets in nested AGENT documents.
       
   403         bool foundBegin = false;
       
   404         if (value == "BEGIN:VCARD") {
       
   405             foundBegin = true;
       
   406         } else if (value.isEmpty()) {
       
   407         } else {
       
   408             property = QVersitProperty();
       
   409             return;
       
   410         }
       
   411         QVersitDocument agentDocument;
       
   412         if (!parseVersitDocument(lineReader, agentDocument, foundBegin)) {
       
   413             property = QVersitProperty();
       
   414         } else {
       
   415             property.setValue(QVariant::fromValue(agentDocument));
       
   416         }
   175     } else {
   417     } else {
   176         int crlfPos = -1;
   418         QTextCodec* codec;
   177         QString encoding(QString::fromAscii("ENCODING"));
   419         QVariant valueVariant(decodeCharset(value, property, lineReader.codec(), &codec));
   178         QString quotedPrintable(QString::fromAscii("QUOTED-PRINTABLE"));
   420         unencode(valueVariant, cursor, property, codec, lineReader);
   179         if (property.parameters().contains(encoding,quotedPrintable)) {
   421         property.setValue(valueVariant);
   180             crlfPos = VersitUtils::findHardLineBreakInQuotedPrintable(text);
   422     }
   181             QByteArray value = text.left(crlfPos);
   423 }
   182             VersitUtils::decodeQuotedPrintable(value);
   424 
   183             // Remove the encoding parameter as the value is now decoded
   425 /*!
   184             property.removeParameter(encoding,quotedPrintable);
   426  * Parses the property according to vCard 3.0 syntax.
   185             property.setValue(value);
   427  */
       
   428 void QVersitReaderPrivate::parseVCard30Property(VersitCursor& cursor, QVersitProperty& property,
       
   429                                                 LineReader& lineReader)
       
   430 {
       
   431     property.setParameters(extractVCard30PropertyParams(cursor, lineReader.codec()));
       
   432 
       
   433     QByteArray value = extractPropertyValue(cursor);
       
   434 
       
   435     QTextCodec* codec;
       
   436     QString valueString(decodeCharset(value, property, lineReader.codec(), &codec));
       
   437     VersitUtils::removeBackSlashEscaping(valueString);
       
   438 
       
   439     if (property.name() == QLatin1String("AGENT")) {
       
   440         // Make a line reader from the value of the property.
       
   441         QByteArray agentValue(codec->fromUnicode(valueString));
       
   442         QBuffer agentData(&agentValue);
       
   443         agentData.open(QIODevice::ReadOnly);
       
   444         agentData.seek(0);
       
   445         LineReader agentLineReader(&agentData, codec);
       
   446 
       
   447         QVersitDocument agentDocument;
       
   448         if (!parseVersitDocument(agentLineReader, agentDocument)) {
       
   449             property = QVersitProperty();
   186         } else {
   450         } else {
   187             crlfPos = text.indexOf("\r\n");
   451             property.setValue(QVariant::fromValue(agentDocument));
   188             QByteArray value = text.left(crlfPos);
   452         }
   189             QString base64(QString::fromAscii("BASE64"));
       
   190             if (property.parameters().contains(encoding,base64)) {
       
   191                 // Remove the linear whitespaces left by vCard 2.1 unfolding
       
   192                 value.replace(' ',"");
       
   193                 value.replace('\t',"");
       
   194             }
       
   195             property.setValue(value);
       
   196         }
       
   197         text = text.mid(crlfPos+2); // +2 is for skipping the CRLF
       
   198     }
       
   199 }
       
   200 
       
   201 /*!
       
   202  * Parses the property according to vCard 3.0 syntax.
       
   203  */
       
   204 void QVersitReaderPrivate::parseVCard30Property(
       
   205     QByteArray& text,
       
   206     QVersitProperty& property)
       
   207 {
       
   208     property.setParameters(VersitUtils::extractVCard30PropertyParams(text));
       
   209     text = VersitUtils::extractPropertyValue(text);
       
   210     int crlfPos = text.indexOf("\r\n");
       
   211     QByteArray value = text.left(crlfPos);
       
   212     VersitUtils::removeBackSlashEscaping(value);
       
   213     if (property.name() == QString::fromAscii("AGENT")) {
       
   214         parseAgentProperty(value,property);
       
   215     } else {
   453     } else {
   216         property.setValue(value);
   454         QVariant valueVariant(valueString);
   217     }
   455         unencode(valueVariant, cursor, property, codec, lineReader);
   218     text = text.mid(crlfPos+2); // +2 is for skipping the CRLF
   456         if (valueVariant.type() == QVariant::ByteArray) {
   219 }
   457             // hack: add the charset parameter back in (even if there wasn't one to start with and
   220 
   458             // the default codec was used).  This will help later on if someone calls valueString()
   221 /*!
   459             // on the property.
   222  * Parses the value of AGENT \a property from \a text
   460             property.insertParameter(QLatin1String("CHARSET"), QLatin1String(codec->name()));
   223  */
   461         }
   224 void QVersitReaderPrivate::parseAgentProperty(
   462         property.setValue(valueVariant);
   225     QByteArray& text,
       
   226     QVersitProperty& property)
       
   227 {
       
   228     QVersitDocument agentDocument;
       
   229     if (!parseVersitDocument(text,agentDocument)) {
       
   230         property = QVersitProperty();
       
   231     } else {
       
   232         property.setEmbeddedDocument(agentDocument);
       
   233     }
   463     }
   234 }
   464 }
   235 
   465 
   236 /*!
   466 /*!
   237  * Sets version to \a document if \a property contains a supported version.
   467  * Sets version to \a document if \a property contains a supported version.
   238  */
   468  */
   239 bool QVersitReaderPrivate::setVersionFromProperty(
   469 bool QVersitReaderPrivate::setVersionFromProperty(QVersitDocument& document, const QVersitProperty& property) const
   240     QVersitDocument& document,
       
   241     const QVersitProperty& property) const
       
   242 {
   470 {
   243     bool valid = true;
   471     bool valid = true;
   244     if (property.name() == QString::fromAscii("VERSION")) {
   472     if (property.name() == QLatin1String("VERSION")) {
   245         QByteArray value = property.value().trimmed();
   473         QString value = property.value().trimmed();
   246         if (property.parameters().contains(
   474         if (property.parameters().contains(QLatin1String("ENCODING"),QLatin1String("BASE64"))
   247                 QString::fromAscii("ENCODING"),QString::fromAscii("BASE64")))
   475             || property.parameters().contains(QLatin1String("TYPE"),QLatin1String("BASE64")))
   248             value = QByteArray::fromBase64(value);
   476             value = QLatin1String(QByteArray::fromBase64(value.toAscii()));
   249         if (value == "2.1") {
   477         if (value == QLatin1String("2.1")) {
   250             document.setVersitType(QVersitDocument::VCard21);
   478             document.setType(QVersitDocument::VCard21Type);
   251         } else if (value == "3.0") {
   479         } else if (value == QLatin1String("3.0")) {
   252             document.setVersitType(QVersitDocument::VCard30);         
   480             document.setType(QVersitDocument::VCard30Type);
   253         } else {
   481         } else {
   254             valid = false;
   482             valid = false;
   255         }
   483         }
   256     } 
   484     } 
   257     return valid;
   485     return valid;
   258 }
   486 }
   259 
   487 
       
   488 /*!
       
   489  * On entry, \a value should hold a QString.  On exit, it may be either a QString or a QByteArray.
       
   490  */
       
   491 void QVersitReaderPrivate::unencode(QVariant& value, VersitCursor& cursor,
       
   492                                     QVersitProperty& property, QTextCodec* codec,
       
   493                                     LineReader& lineReader) const
       
   494 {
       
   495     Q_ASSERT(value.type() == QVariant::String);
       
   496 
       
   497     QString valueString = value.toString();
       
   498 
       
   499     if (property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("QUOTED-PRINTABLE"))) {
       
   500         // At this point, we need to accumulate bytes until we hit a real line break (no = before
       
   501         // it) value already contains everything up to the character before the newline
       
   502         while (valueString.endsWith(QLatin1Char('='))) {
       
   503             valueString.chop(1); // Get rid of '='
       
   504             // We add each line (minus the escaped = and newline chars)
       
   505             cursor = lineReader.readLine();
       
   506             QString line = codec->toUnicode(
       
   507                     cursor.data.mid(cursor.position, cursor.selection-cursor.position));
       
   508             valueString.append(line);
       
   509         }
       
   510         decodeQuotedPrintable(valueString);
       
   511         // Remove the encoding parameter as the value is now decoded
       
   512         property.removeParameters(QLatin1String("ENCODING"));
       
   513         value.setValue(valueString);
       
   514     } else if (property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("BASE64"))
       
   515         || property.parameters().contains(QLatin1String("ENCODING"), QLatin1String("B"))
       
   516         || property.parameters().contains(QLatin1String("TYPE"), QLatin1String("BASE64"))
       
   517         || property.parameters().contains(QLatin1String("TYPE"), QLatin1String("B"))) {
       
   518         value.setValue(QByteArray::fromBase64(valueString.toAscii()));
       
   519         // Remove the encoding parameter as the value is now decoded
       
   520         property.removeParameters(QLatin1String("ENCODING"));
       
   521         // Hack: add the charset parameter back in (even if there wasn't one to start with and
       
   522         // the default codec was used).  This will help later on if someone calls valueString()
       
   523         // on the property.
       
   524         property.insertParameter(QLatin1String("CHARSET"), QLatin1String(codec->name()));
       
   525     }
       
   526 }
       
   527 
       
   528 /*!
       
   529  * Decodes \a value, after working out what charset it is in using the context of \a property and
       
   530  * returns it.  The codec used to decode is returned in \a codec.
       
   531  */
       
   532 QString QVersitReaderPrivate::decodeCharset(const QByteArray& value,
       
   533                                             QVersitProperty& property,
       
   534                                             QTextCodec* defaultCodec,
       
   535                                             QTextCodec** codec) const
       
   536 {
       
   537     const QString charset(QLatin1String("CHARSET"));
       
   538     if (property.parameters().contains(charset)) {
       
   539         QString charsetValue = *property.parameters().find(charset);
       
   540         property.removeParameters(charset);
       
   541         *codec = QTextCodec::codecForName(charsetValue.toAscii());
       
   542         if (*codec != NULL) {
       
   543             return (*codec)->toUnicode(value);
       
   544         } else {
       
   545             *codec = defaultCodec;
       
   546             return defaultCodec->toUnicode(value);
       
   547         }
       
   548     }
       
   549     *codec = defaultCodec;
       
   550     return defaultCodec->toUnicode(value);
       
   551 }
       
   552 
       
   553 /*!
       
   554  * Decodes Quoted-Printable encoded (RFC 1521) characters in /a text.
       
   555  */
       
   556 void QVersitReaderPrivate::decodeQuotedPrintable(QString& text) const
       
   557 {
       
   558     for (int i=0; i < text.length(); i++) {
       
   559         QChar current = text.at(i);
       
   560         if (current == QLatin1Char('=') && i+2 < text.length()) {
       
   561             int next = text.at(i+1).unicode();
       
   562             int nextAfterNext = text.at(i+2).unicode();
       
   563             if (((next >= 'a' && next <= 'f') ||
       
   564                  (next >= 'A' && next <= 'F') ||
       
   565                  (next >= '0' && next <= '9')) &&
       
   566                 ((nextAfterNext >= 'a' && nextAfterNext <= 'f') ||
       
   567                  (nextAfterNext >= 'A' && nextAfterNext <= 'F') ||
       
   568                  (nextAfterNext >= '0' && nextAfterNext <= '9'))) {
       
   569                 bool ok;
       
   570                 QChar decodedChar(text.mid(i+1, 2).toInt(&ok,16));
       
   571                 if (ok)
       
   572                     text.replace(i, 3, decodedChar);
       
   573             } else if (next == '\r' && nextAfterNext == '\n') {
       
   574                 // Newlines can still be found here if they are encoded in a non-default charset.
       
   575                 text.remove(i, 3);
       
   576             }
       
   577         }
       
   578     }
       
   579 }
       
   580 
       
   581 
       
   582 /*!
       
   583  * Extracts the groups and the name of the property using \a codec to determine the delimiters
       
   584  *
       
   585  * On entry, \a line should select a whole line.
       
   586  * On exit, \a line will be updated to point after the groups and name.
       
   587  */
       
   588 QPair<QStringList,QString>QVersitReaderPrivate::extractPropertyGroupsAndName(
       
   589         VersitCursor& line, QTextCodec *codec) const
       
   590 {
       
   591     const QByteArray semicolon = VersitUtils::encode(';', codec);
       
   592     const QByteArray colon = VersitUtils::encode(':', codec);
       
   593     const QByteArray backslash = VersitUtils::encode('\\', codec);
       
   594     QPair<QStringList,QString> groupsAndName;
       
   595     int length = 0;
       
   596     Q_ASSERT(line.data.size() >= line.position);
       
   597 
       
   598     int separatorLength = semicolon.length();
       
   599     for (int i = line.position; i < line.selection - separatorLength + 1; i++) {
       
   600         if ((containsAt(line.data, semicolon, i)
       
   601                 && !containsAt(line.data, backslash, i-separatorLength))
       
   602             || containsAt(line.data, colon, i)) {
       
   603             length = i - line.position;
       
   604             break;
       
   605         }
       
   606     }
       
   607     if (length > 0) {
       
   608         QString trimmedGroupsAndName =
       
   609                 codec->toUnicode(line.data.mid(line.position, length)).trimmed();
       
   610         QStringList parts = trimmedGroupsAndName.split(QLatin1Char('.'));
       
   611         if (parts.count() > 1) {
       
   612             groupsAndName.second = parts.takeLast();
       
   613             groupsAndName.first = parts;
       
   614         } else {
       
   615             groupsAndName.second = trimmedGroupsAndName;
       
   616         }
       
   617         line.setPosition(length + line.position);
       
   618     }
       
   619 
       
   620     return groupsAndName;
       
   621 }
       
   622 
       
   623 /*!
       
   624  * Extracts the value of the property.
       
   625  * Returns an empty string if the value was not found.
       
   626  *
       
   627  * On entry \a line should point to the value anyway.
       
   628  * On exit \a line should point to newline after the value
       
   629  */
       
   630 QByteArray QVersitReaderPrivate::extractPropertyValue(VersitCursor& line) const
       
   631 {
       
   632     QByteArray value = line.data.mid(line.position, line.selection - line.position);
       
   633 
       
   634     /* Now advance the cursor in all cases. */
       
   635     line.position = line.selection;
       
   636     return value;
       
   637 }
       
   638 
       
   639 /*!
       
   640  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
       
   641  * The parameters without names are added as "TYPE" parameters.
       
   642  *
       
   643  * On entry \a line should contain the entire line.
       
   644  * On exit, line will be updated to point to the start of the value.
       
   645  */
       
   646 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard21PropertyParams(
       
   647         VersitCursor& line, QTextCodec *codec) const
       
   648 {
       
   649     QMultiHash<QString,QString> result;
       
   650     QList<QByteArray> paramList = extractParams(line, codec);
       
   651     while (!paramList.isEmpty()) {
       
   652         QByteArray param = paramList.takeLast();
       
   653         QString name = paramName(param, codec);
       
   654         QString value = paramValue(param, codec);
       
   655         result.insert(name,value);
       
   656     }
       
   657 
       
   658     return result;
       
   659 }
       
   660 
       
   661 /*!
       
   662  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
       
   663  * The parameters without names are added as "TYPE" parameters.
       
   664  */
       
   665 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard30PropertyParams(
       
   666         VersitCursor& line, QTextCodec *codec) const
       
   667 {
       
   668     QMultiHash<QString,QString> result;
       
   669     QList<QByteArray> paramList = extractParams(line, codec);
       
   670     while (!paramList.isEmpty()) {
       
   671         QByteArray param = paramList.takeLast();
       
   672         QString name(paramName(param, codec));
       
   673         VersitUtils::removeBackSlashEscaping(name);
       
   674         QString values = paramValue(param, codec);
       
   675         QList<QString> valueList = values.split(QLatin1Char(','), QString::SkipEmptyParts);
       
   676         QString buffer; // for any part ending in a backslash, join it to the next.
       
   677         foreach (QString value, valueList) {
       
   678             if (value.endsWith(QLatin1Char('\\')) && !value.endsWith(QLatin1String("\\\\"))) {
       
   679                 value.chop(1);
       
   680                 buffer.append(value);
       
   681                 buffer.append(QLatin1Char(',')); // because the comma got nuked by split()
       
   682             }
       
   683             else {
       
   684                 buffer.append(value);
       
   685                 VersitUtils::removeBackSlashEscaping(buffer);
       
   686                 result.insert(name, buffer);
       
   687                 buffer.clear();
       
   688             }
       
   689         }
       
   690     }
       
   691 
       
   692     return result;
       
   693 }
       
   694 
       
   695 
       
   696 /*!
       
   697  * Extracts the parameters as delimited by semicolons using \a codec to determine the delimiters.
       
   698  *
       
   699  * On entry \a line should point to the start of the parameter section (past the name).
       
   700  * On exit, \a line will be updated to point to the start of the value.
       
   701  */
       
   702 QList<QByteArray> QVersitReaderPrivate::extractParams(VersitCursor& line, QTextCodec *codec) const
       
   703 {
       
   704     const QByteArray colon = VersitUtils::encode(':', codec);
       
   705     QList<QByteArray> params;
       
   706 
       
   707     /* find the end of the name&params */
       
   708     int colonIndex = line.data.indexOf(colon, line.position);
       
   709     if (colonIndex > line.position && colonIndex < line.selection) {
       
   710         QByteArray nameAndParamsString = line.data.mid(line.position, colonIndex - line.position);
       
   711         params = extractParts(nameAndParamsString, VersitUtils::encode(';', codec), codec);
       
   712 
       
   713         /* Update line */
       
   714         line.setPosition(colonIndex + colon.length());
       
   715     } else if (colonIndex == line.position) {
       
   716         // No parameters.. advance past it
       
   717         line.setPosition(line.position + colon.length());
       
   718     }
       
   719 
       
   720     return params;
       
   721 }
       
   722 
       
   723 /*!
       
   724  * Extracts the parts separated by separator discarding the separators escaped with a backslash
       
   725  * encoded with \a codec
       
   726  */
       
   727 QList<QByteArray> QVersitReaderPrivate::extractParts(
       
   728         const QByteArray& text, const QByteArray& separator, QTextCodec* codec) const
       
   729 {
       
   730     QList<QByteArray> parts;
       
   731     int partStartIndex = 0;
       
   732     int textLength = text.length();
       
   733     int separatorLength = separator.length();
       
   734     QByteArray backslash = VersitUtils::encode('\\', codec);
       
   735     int backslashLength = backslash.length();
       
   736 
       
   737     for (int i=0; i < textLength-separatorLength+1; i++) {
       
   738         if (containsAt(text, separator, i)
       
   739             && (i < backslashLength
       
   740                 || !containsAt(text, backslash, i-backslashLength))) {
       
   741             int length = i-partStartIndex;
       
   742             QByteArray part = extractPart(text,partStartIndex,length);
       
   743             if (part.length() > 0)
       
   744                 parts.append(part);
       
   745             partStartIndex = i+separatorLength;
       
   746         }
       
   747     }
       
   748 
       
   749     // Add the last or only part
       
   750     QByteArray part = extractPart(text,partStartIndex);
       
   751     if (part.length() > 0)
       
   752         parts.append(part);
       
   753     return parts;
       
   754 }
       
   755 
       
   756 /*!
       
   757  * Extracts a substring limited by /a startPosition and /a length.
       
   758  */
       
   759 QByteArray QVersitReaderPrivate::extractPart(
       
   760         const QByteArray& text, int startPosition, int length) const
       
   761 {
       
   762     QByteArray part;
       
   763     if (startPosition >= 0)
       
   764         part = text.mid(startPosition,length).trimmed();
       
   765     return part;
       
   766 }
       
   767 
       
   768 /*!
       
   769  * Extracts the name of the parameter using \a codec to determine the delimiters.
       
   770  * No name is interpreted as an implicit "TYPE".
       
   771  */
       
   772 QString QVersitReaderPrivate::paramName(const QByteArray& parameter, QTextCodec* codec) const
       
   773 {
       
   774      if (parameter.trimmed().length() == 0)
       
   775          return QString();
       
   776      QByteArray equals = VersitUtils::encode('=', codec);
       
   777      int equalsIndex = parameter.indexOf(equals);
       
   778      if (equalsIndex > 0) {
       
   779          return codec->toUnicode(parameter.left(equalsIndex)).trimmed();
       
   780      }
       
   781 
       
   782      return QLatin1String("TYPE");
       
   783 }
       
   784 
       
   785 /*!
       
   786  * Extracts the value of the parameter using \a codec to determine the delimiters
       
   787  */
       
   788 QString QVersitReaderPrivate::paramValue(const QByteArray& parameter, QTextCodec* codec) const
       
   789 {
       
   790     QByteArray value(parameter);
       
   791     QByteArray equals = VersitUtils::encode('=', codec);
       
   792     int equalsIndex = parameter.indexOf(equals);
       
   793     if (equalsIndex > 0) {
       
   794         int valueLength = parameter.length() - (equalsIndex + equals.length());
       
   795         value = parameter.right(valueLength).trimmed();
       
   796     }
       
   797 
       
   798     return codec->toUnicode(value);
       
   799 }
       
   800 
       
   801 /*!
       
   802  * Returns true if and only if \a text contains \a ba at \a index
       
   803  *
       
   804  * On entry, index must be >= 0
       
   805  */
       
   806 bool QVersitReaderPrivate::containsAt(const QByteArray& text, const QByteArray& match, int index)
       
   807 {
       
   808     int n = match.length();
       
   809     if (text.length() - index < n)
       
   810         return false;
       
   811     const char* textData = text.constData();
       
   812     const char* matchData = match.constData();
       
   813     return memcmp(textData+index, matchData, n) == 0;
       
   814 }
   260 
   815 
   261 #include "moc_qversitreader_p.cpp"
   816 #include "moc_qversitreader_p.cpp"
   262 
       
   263 QTM_END_NAMESPACE