src/versit/qversitreader_p.cpp
changeset 0 876b1a06bc25
child 5 603d3f8b6302
equal deleted inserted replaced
-1:000000000000 0:876b1a06bc25
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Mobility Components.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qversitreader_p.h"
       
    43 #include "qversitdocument.h"
       
    44 #include "versitutils_p.h"
       
    45 #include "qmobilityglobal.h"
       
    46 #include <QTextCodec>
       
    47 #include <QMutexLocker>
       
    48 #include <QVariant>
       
    49 #include <QBuffer>
       
    50 
       
    51 QTM_USE_NAMESPACE
       
    52 
       
    53 // Some big enough value for nested versit documents to prevent infinite recursion
       
    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   \internal
       
    60 
       
    61   This class keeps an internal buffer which it uses to temporarily store data which it has read from
       
    62   the device but not returned to the user.
       
    63  */
       
    64 
       
    65 /*!
       
    66   Constructs a LineReader that reads from the given \a device using the given \a codec.
       
    67   \a chunkSize is the number of bytes to read at a time (it is useful for testing but shouldn't
       
    68   otherwise be set).
       
    69   */
       
    70 LineReader::LineReader(QIODevice* device, QTextCodec *codec, int chunkSize)
       
    71     : mDevice(device),
       
    72     mCodec(codec),
       
    73     mChunkSize(chunkSize),
       
    74     mCrlfList(*VersitUtils::newlineList(mCodec)),
       
    75     mBuffer(VersitCursor(QByteArray())),
       
    76     mOdometer(0)
       
    77 {
       
    78 }
       
    79 
       
    80 /*!
       
    81   Attempts to read a line and returns a VersitCursor describing the line.  The cursor returned
       
    82   includes the data, as well as the position and selection index bounds.  Data within those bounds
       
    83   represents the line.  Data outside those bounds should not be used.
       
    84  */
       
    85 VersitCursor LineReader::readLine()
       
    86 {
       
    87     mBuffer.position = mBuffer.selection;
       
    88     mSearchFrom = mBuffer.position;
       
    89 
       
    90     // First, look for a newline in the already-existing buffer.  If found, return the line.
       
    91     if (tryReadLine(mBuffer, false)) {
       
    92         mBuffer.dropOldData();
       
    93         mOdometer += mBuffer.selection - mBuffer.position;
       
    94         return mBuffer;
       
    95     }
       
    96 
       
    97     // Otherwise, keep reading more data until either a CRLF is found, or there's no more to read.
       
    98     while (!mDevice->atEnd()) {
       
    99         QByteArray temp = mDevice->read(mChunkSize);
       
   100         if (!temp.isEmpty()) {
       
   101             mBuffer.data.append(temp);
       
   102             if (tryReadLine(mBuffer, false)) {
       
   103                 mBuffer.dropOldData();
       
   104                 mOdometer += mBuffer.selection - mBuffer.position;
       
   105                 return mBuffer;
       
   106             }
       
   107         } else {
       
   108             mDevice->waitForReadyRead(500);
       
   109         }
       
   110     }
       
   111 
       
   112     // We've reached the end of the stream.  Find a newline from the buffer (or return what's left).
       
   113     tryReadLine(mBuffer, true);
       
   114     mBuffer.dropOldData();
       
   115     mOdometer += mBuffer.selection - mBuffer.position;
       
   116     return mBuffer;
       
   117 }
       
   118 
       
   119 /*!
       
   120   How many bytes have been returned in the VersitCursor in the lifetime of the LineReader.
       
   121  */
       
   122 int LineReader::odometer()
       
   123 {
       
   124     return mOdometer;
       
   125 }
       
   126 
       
   127 /*!
       
   128   Returns true if there are no more lines left for readLine() to return.  It is possible for atEnd()
       
   129   to return false and for there to be no more data left (eg. if there are trailing newlines at the
       
   130   end of the input.  In this case, readLine() will return an empty line (ie. position == selection).
       
   131  */
       
   132 bool LineReader::atEnd()
       
   133 {
       
   134     return mDevice->atEnd() && mBuffer.selection == mBuffer.data.size();
       
   135 }
       
   136 
       
   137 /*!
       
   138   Returns the codec that the LineReader reads with.
       
   139  */
       
   140 QTextCodec* LineReader::codec()
       
   141 {
       
   142     return mCodec;
       
   143 }
       
   144 
       
   145 /*!
       
   146  * Get the next line of input from \a device to parse.  Also performs unfolding by removing
       
   147  * sequences of newline-space from the retrieved line.  Skips over any newlines at the start of the
       
   148  * input.
       
   149  *
       
   150  * Returns a VersitCursor containing and selecting the line.
       
   151  */
       
   152 bool LineReader::tryReadLine(VersitCursor &cursor, bool atEnd)
       
   153 {
       
   154     int crlfPos = -1;
       
   155 
       
   156     QByteArray space = VersitUtils::encode(' ', mCodec);
       
   157     QByteArray tab = VersitUtils::encode('\t', mCodec);
       
   158     int spaceLength = space.length();
       
   159 
       
   160     forever {
       
   161         foreach(const QByteArrayMatcher& crlf, mCrlfList) {
       
   162             int crlfLength = crlf.pattern().length();
       
   163             crlfPos = crlf.indexIn(cursor.data, mSearchFrom);
       
   164             if (crlfPos == cursor.position) {
       
   165                 // Newline at start of line.  Set position to directly after it.
       
   166                 cursor.position += crlfLength;
       
   167                 mSearchFrom = cursor.position;
       
   168                 break;
       
   169             } else if (crlfPos > cursor.position) {
       
   170                 // Found the CRLF.
       
   171                 if (QVersitReaderPrivate::containsAt(cursor.data, space, crlfPos + crlfLength)
       
   172                     || QVersitReaderPrivate::containsAt(cursor.data, tab, crlfPos + crlfLength)) {
       
   173                     // If it's followed by whitespace, collapse it.
       
   174                     cursor.data.remove(crlfPos, crlfLength + spaceLength);
       
   175                     mSearchFrom = crlfPos;
       
   176                     break;
       
   177                 } else if (!atEnd && crlfPos + crlfLength + spaceLength >= cursor.data.size()) {
       
   178                     // If our CRLF is at the end of the current buffer but there's more to read,
       
   179                     // it's possible that a space could be hiding on the next read from the device.
       
   180                     // Just pretend we didn't see the CRLF and pick it up the next time round.
       
   181                     mSearchFrom = crlfPos;
       
   182                     return false;
       
   183                 } else {
       
   184                     // Found the CRLF.
       
   185                     cursor.selection = crlfPos;
       
   186                     return true;
       
   187                 }
       
   188             }
       
   189         }
       
   190         if (crlfPos == -1) {
       
   191             // No CRLF found.
       
   192             cursor.selection = cursor.data.size();
       
   193             return false;
       
   194         }
       
   195     }
       
   196 }
       
   197 
       
   198 /*! Links the signals from this to the signals of \a reader. */
       
   199 void QVersitReaderPrivate::init(QVersitReader* reader)
       
   200 {
       
   201     qRegisterMetaType<QVersitReader::State>("QVersitReader::State");
       
   202     connect(this, SIGNAL(stateChanged(QVersitReader::State)),
       
   203             reader, SIGNAL(stateChanged(QVersitReader::State)),Qt::DirectConnection);
       
   204     connect(this, SIGNAL(resultsAvailable()),
       
   205             reader, SIGNAL(resultsAvailable()), Qt::DirectConnection);
       
   206 }
       
   207 
       
   208 /*! Construct a reader. */
       
   209 QVersitReaderPrivate::QVersitReaderPrivate()
       
   210     : mIoDevice(0),
       
   211     mDocumentNestingLevel(0),
       
   212     mDefaultCodec(QTextCodec::codecForName("UTF-8")),
       
   213     mState(QVersitReader::InactiveState),
       
   214     mError(QVersitReader::NoError),
       
   215     mIsCanceling(false)
       
   216 {
       
   217     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("AGENT")),
       
   218                          QVersitProperty::VersitDocumentType);
       
   219     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("AGENT")),
       
   220                          QVersitProperty::VersitDocumentType);
       
   221     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("N")),
       
   222                          QVersitProperty::CompoundType);
       
   223     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("N")),
       
   224                          QVersitProperty::CompoundType);
       
   225     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ADR")),
       
   226                          QVersitProperty::CompoundType);
       
   227     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ADR")),
       
   228                          QVersitProperty::CompoundType);
       
   229     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("GEO")),
       
   230                          QVersitProperty::CompoundType);
       
   231     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("GEO")),
       
   232                          QVersitProperty::CompoundType);
       
   233     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("ORG")),
       
   234                          QVersitProperty::CompoundType);
       
   235     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("ORG")),
       
   236                          QVersitProperty::CompoundType);
       
   237     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("NICKNAME")),
       
   238                          QVersitProperty::ListType);
       
   239     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("NICKNAME")),
       
   240                          QVersitProperty::ListType);
       
   241     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("CATEGORIES")),
       
   242                          QVersitProperty::ListType);
       
   243     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("CATEGORIES")),
       
   244                          QVersitProperty::ListType);
       
   245     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("X-CHILDREN")),
       
   246                          QVersitProperty::ListType);
       
   247     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("X-CHILDREN")),
       
   248                          QVersitProperty::ListType);
       
   249     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard21Type, QString::fromAscii("X-NICKNAME")),
       
   250                          QVersitProperty::ListType);
       
   251     mValueTypeMap.insert(qMakePair(QVersitDocument::VCard30Type, QString::fromAscii("X-NICKNAME")),
       
   252                          QVersitProperty::ListType);
       
   253 }
       
   254 
       
   255 /*! Destroy a reader. */
       
   256 QVersitReaderPrivate::~QVersitReaderPrivate()
       
   257 {
       
   258 }
       
   259 
       
   260 /*!
       
   261  * Inherited from QThread, called by QThread when the thread has been started.
       
   262  */
       
   263 void QVersitReaderPrivate::run()
       
   264 {
       
   265     read();
       
   266 }
       
   267 
       
   268 /*!
       
   269  * Does the actual reading and sets the error and state as appropriate.
       
   270  * If \a async, then stateChanged() signals are emitted as the reading happens.
       
   271  */
       
   272 void QVersitReaderPrivate::read()
       
   273 {
       
   274     mMutex.lock();
       
   275     mVersitDocuments.clear();
       
   276     mMutex.unlock();
       
   277     bool canceled = false;
       
   278 
       
   279     LineReader lineReader(mIoDevice, mDefaultCodec);
       
   280     while(!lineReader.atEnd()) {
       
   281         if (isCanceling()) {
       
   282             canceled = true;
       
   283             break;
       
   284         }
       
   285         QVersitDocument document;
       
   286         int oldPos = lineReader.odometer();
       
   287         bool ok = parseVersitDocument(lineReader, document);
       
   288 
       
   289         if (ok) {
       
   290             if (document.isEmpty())
       
   291                 break;
       
   292             else {
       
   293                 QMutexLocker locker(&mMutex);
       
   294                 mVersitDocuments.append(document);
       
   295                 emit resultsAvailable();
       
   296             }
       
   297         } else {
       
   298             setError(QVersitReader::ParseError);
       
   299             if (lineReader.odometer() == oldPos)
       
   300                 break;
       
   301         }
       
   302     };
       
   303     if (canceled)
       
   304         setState(QVersitReader::CanceledState);
       
   305     else
       
   306         setState(QVersitReader::FinishedState);
       
   307 }
       
   308 
       
   309 void QVersitReaderPrivate::setState(QVersitReader::State state)
       
   310 {
       
   311     mMutex.lock();
       
   312     mState = state;
       
   313     mMutex.unlock();
       
   314     emit stateChanged(state);
       
   315 }
       
   316 
       
   317 QVersitReader::State QVersitReaderPrivate::state() const
       
   318 {
       
   319     QMutexLocker locker(&mMutex);
       
   320     return mState;
       
   321 }
       
   322 
       
   323 void QVersitReaderPrivate::setError(QVersitReader::Error error)
       
   324 {
       
   325     QMutexLocker locker(&mMutex);
       
   326     mError = error;
       
   327 }
       
   328 
       
   329 QVersitReader::Error QVersitReaderPrivate::error() const
       
   330 {
       
   331     QMutexLocker locker(&mMutex);
       
   332     return mError;
       
   333 }
       
   334 
       
   335 void QVersitReaderPrivate::setCanceling(bool canceling)
       
   336 {
       
   337     QMutexLocker locker(&mMutex);
       
   338     mIsCanceling = canceling;
       
   339 }
       
   340 
       
   341 bool QVersitReaderPrivate::isCanceling()
       
   342 {
       
   343     QMutexLocker locker(&mMutex);
       
   344     return mIsCanceling;
       
   345 }
       
   346 
       
   347 /*!
       
   348  * Parses a versit document. Returns true if the parsing was successful.
       
   349  */
       
   350 bool QVersitReaderPrivate::parseVersitDocument(LineReader& lineReader, QVersitDocument& document,
       
   351                                                bool foundBegin)
       
   352 {
       
   353     if (mDocumentNestingLevel >= MAX_VERSIT_DOCUMENT_NESTING_DEPTH)
       
   354         return false; // To prevent infinite recursion
       
   355 
       
   356     bool parsingOk = true;
       
   357     mDocumentNestingLevel++;
       
   358 
       
   359     // TODO: Various readers should be made subclasses and eliminate assumptions like this.
       
   360     // We don't know what type it is: just assume it's a vCard 3.0
       
   361     document.setType(QVersitDocument::VCard30Type);
       
   362 
       
   363     QVersitProperty property;
       
   364 
       
   365     if (!foundBegin) {
       
   366         property = parseNextVersitProperty(document.type(), lineReader);
       
   367         if (property.name() == QLatin1String("BEGIN")
       
   368             && property.value().trimmed().toUpper() == QLatin1String("VCARD")) {
       
   369             foundBegin = true;
       
   370         } else if (property.isEmpty()) {
       
   371             // A blank document (or end of file) was found.
       
   372             document = QVersitDocument();
       
   373         } else {
       
   374             // Some property other than BEGIN was found.
       
   375             parsingOk = false;
       
   376         }
       
   377     }
       
   378 
       
   379     if (foundBegin) {
       
   380         do {
       
   381             /* Grab it */
       
   382             property = parseNextVersitProperty(document.type(), lineReader);
       
   383 
       
   384             /* Discard embedded vcard documents - not supported yet.  Discard the entire vCard */
       
   385             if (property.name() == QLatin1String("BEGIN") &&
       
   386                 QString::compare(property.value().trimmed(),
       
   387                                  QLatin1String("VCARD"), Qt::CaseInsensitive) == 0) {
       
   388                 parsingOk = false;
       
   389                 QVersitDocument nestedDocument;
       
   390                 if (!parseVersitDocument(lineReader, nestedDocument, true))
       
   391                     break;
       
   392             }
       
   393 
       
   394             // See if this is a version property and continue parsing under that version
       
   395             if (!setVersionFromProperty(document, property)) {
       
   396                 parsingOk = false;
       
   397                 break;
       
   398             }
       
   399 
       
   400             /* Nope, something else.. just add it */
       
   401             if (property.name() != QLatin1String("VERSION") &&
       
   402                 property.name() != QLatin1String("END"))
       
   403                 document.addProperty(property);
       
   404         } while (property.name().length() > 0 && property.name() != QLatin1String("END"));
       
   405         if (property.name() != QLatin1String("END"))
       
   406             parsingOk = false;
       
   407     }
       
   408     mDocumentNestingLevel--;
       
   409     if (!parsingOk)
       
   410         document = QVersitDocument();
       
   411 
       
   412     return parsingOk;
       
   413 }
       
   414 
       
   415 /*!
       
   416  * Parses a versit document and returns whether parsing succeeded.
       
   417  */
       
   418 QVersitProperty QVersitReaderPrivate::parseNextVersitProperty(
       
   419         QVersitDocument::VersitType versitType,
       
   420         LineReader& lineReader)
       
   421 {
       
   422     VersitCursor cursor = lineReader.readLine();
       
   423     if (cursor.position >= cursor.selection)
       
   424         return QVersitProperty();
       
   425 
       
   426     // Otherwise, do stuff.
       
   427     QPair<QStringList,QString> groupsAndName =
       
   428             extractPropertyGroupsAndName(cursor, lineReader.codec());
       
   429 
       
   430     QVersitProperty property;
       
   431     property.setGroups(groupsAndName.first);
       
   432     property.setName(groupsAndName.second);
       
   433     // set the propertyValueType
       
   434     QPair<QVersitDocument::VersitType, QString> key =
       
   435         qMakePair(versitType, property.name());
       
   436     if (mValueTypeMap.contains(key))
       
   437         property.setValueType(mValueTypeMap.value(key));
       
   438 
       
   439     if (versitType == QVersitDocument::VCard21Type)
       
   440         parseVCard21Property(cursor, property, lineReader);
       
   441     else if (versitType == QVersitDocument::VCard30Type)
       
   442         parseVCard30Property(cursor, property, lineReader);
       
   443 
       
   444     return property;
       
   445 }
       
   446 
       
   447 /*!
       
   448  * Parses the property according to vCard 2.1 syntax.
       
   449  */
       
   450 void QVersitReaderPrivate::parseVCard21Property(VersitCursor& cursor, QVersitProperty& property,
       
   451                                                 LineReader& lineReader)
       
   452 {
       
   453     property.setParameters(extractVCard21PropertyParams(cursor, lineReader.codec()));
       
   454 
       
   455     QByteArray value = extractPropertyValue(cursor);
       
   456     if (property.valueType() == QVersitProperty::VersitDocumentType) {
       
   457         // Hack to handle cases where start of document is on the same or next line as "AGENT:"
       
   458         bool foundBegin = false;
       
   459         if (value == "BEGIN:VCARD") {
       
   460             foundBegin = true;
       
   461         } else if (value.isEmpty()) {
       
   462         } else {
       
   463             property = QVersitProperty();
       
   464             return;
       
   465         }
       
   466         QVersitDocument subDocument;
       
   467         if (!parseVersitDocument(lineReader, subDocument, foundBegin)) {
       
   468             property = QVersitProperty();
       
   469         } else {
       
   470             property.setValue(QVariant::fromValue(subDocument));
       
   471         }
       
   472     } else {
       
   473         QTextCodec* codec;
       
   474         bool isBinary = unencode(value, cursor, property, lineReader);
       
   475         if (isBinary) {
       
   476             property.setValue(value);
       
   477             property.setValueType(QVersitProperty::BinaryType);
       
   478         }
       
   479         else {
       
   480             property.setValue(decodeCharset(value, property, lineReader.codec(), &codec));
       
   481             splitStructuredValue(property, false);
       
   482         }
       
   483     }
       
   484 }
       
   485 
       
   486 /*!
       
   487  * Parses the property according to vCard 3.0 syntax.
       
   488  */
       
   489 void QVersitReaderPrivate::parseVCard30Property(VersitCursor& cursor, QVersitProperty& property,
       
   490                                                 LineReader& lineReader)
       
   491 {
       
   492     property.setParameters(extractVCard30PropertyParams(cursor, lineReader.codec()));
       
   493 
       
   494     QByteArray value = extractPropertyValue(cursor);
       
   495 
       
   496     QTextCodec* codec;
       
   497 
       
   498     if (property.valueType() == QVersitProperty::VersitDocumentType) {
       
   499         QString valueString(decodeCharset(value, property, lineReader.codec(), &codec));
       
   500         removeBackSlashEscaping(valueString);
       
   501         // Make a line reader from the value of the property.
       
   502         QByteArray subDocumentValue(codec->fromUnicode(valueString));
       
   503         QBuffer subDocumentData(&subDocumentValue);
       
   504         subDocumentData.open(QIODevice::ReadOnly);
       
   505         subDocumentData.seek(0);
       
   506         LineReader subDocumentLineReader(&subDocumentData, codec);
       
   507 
       
   508         QVersitDocument subDocument;
       
   509         if (!parseVersitDocument(subDocumentLineReader, subDocument)) {
       
   510             property = QVersitProperty();
       
   511         } else {
       
   512             property.setValue(QVariant::fromValue(subDocument));
       
   513         }
       
   514     } else {
       
   515         bool isBinary = unencode(value, cursor, property, lineReader);
       
   516         if (isBinary) {
       
   517             property.setValue(value);
       
   518             property.setValueType(QVersitProperty::BinaryType);
       
   519         } else {
       
   520             property.setValue(decodeCharset(value, property, lineReader.codec(), &codec));
       
   521             bool isList = splitStructuredValue(property, true);
       
   522             // Do backslash unescaping
       
   523             if (isList) {
       
   524                 QStringList list = property.value<QStringList>();
       
   525                 for (int i = 0; i < list.length(); i++) {
       
   526                     removeBackSlashEscaping(list[i]);
       
   527                 }
       
   528                 property.setValue(list);
       
   529             } else {
       
   530                 QString value = property.value();
       
   531                 removeBackSlashEscaping(value);
       
   532                 property.setValue(value);
       
   533             }
       
   534         }
       
   535     }
       
   536 }
       
   537 
       
   538 /*!
       
   539  * Sets version to \a document if \a property contains a supported version.
       
   540  */
       
   541 bool QVersitReaderPrivate::setVersionFromProperty(QVersitDocument& document, const QVersitProperty& property) const
       
   542 {
       
   543     bool valid = true;
       
   544     if (property.name() == QLatin1String("VERSION")) {
       
   545         QString value = property.value().trimmed();
       
   546         QStringList encodingParameters = property.parameters().values(QLatin1String("ENCODING"));
       
   547         QStringList typeParameters = property.parameters().values(QLatin1String("TYPE"));
       
   548         if (encodingParameters.contains(QLatin1String("BASE64"), Qt::CaseInsensitive)
       
   549             || typeParameters.contains(QLatin1String("BASE64"), Qt::CaseInsensitive))
       
   550             value = QLatin1String(QByteArray::fromBase64(value.toAscii()));
       
   551         if (value == QLatin1String("2.1")) {
       
   552             document.setType(QVersitDocument::VCard21Type);
       
   553         } else if (value == QLatin1String("3.0")) {
       
   554             document.setType(QVersitDocument::VCard30Type);
       
   555         } else {
       
   556             valid = false;
       
   557         }
       
   558     }
       
   559     return valid;
       
   560 }
       
   561 
       
   562 /*!
       
   563  * On entry, \a value should be the byte array to unencode.  It is modified to be the unencoded
       
   564  * version.  Returns true if and only if the value was base-64 encoded.  \a cursor and
       
   565  * \a lineReader are supplied in case more lines need to be read (for quoted-printable).  The
       
   566  * \a property is supplied so we know what kind of encoding was used.
       
   567  */
       
   568 bool QVersitReaderPrivate::unencode(QByteArray& value, VersitCursor& cursor,
       
   569                                     QVersitProperty& property,
       
   570                                     LineReader& lineReader) const
       
   571 {
       
   572     QStringList encodingParameters = property.parameters().values(QLatin1String("ENCODING"));
       
   573     QStringList typeParameters = property.parameters().values(QLatin1String("TYPE"));
       
   574     if (encodingParameters.contains(QLatin1String("QUOTED-PRINTABLE"), Qt::CaseInsensitive)) {
       
   575         // At this point, we need to accumulate bytes until we hit a real line break (no = before
       
   576         // it) value already contains everything up to the character before the newline
       
   577         while (value.endsWith('=')) {
       
   578             value.chop(1); // Get rid of '='
       
   579             // We add each line (minus the escaped = and newline chars)
       
   580             cursor = lineReader.readLine();
       
   581             QByteArray line = cursor.data.mid(cursor.position, cursor.selection-cursor.position);
       
   582             value.append(line);
       
   583         }
       
   584         decodeQuotedPrintable(value);
       
   585         // Remove the encoding parameter as the value is now decoded
       
   586         property.removeParameters(QLatin1String("ENCODING"));
       
   587         return false;
       
   588     } else if (encodingParameters.contains(QLatin1String("BASE64"), Qt::CaseInsensitive)
       
   589         || encodingParameters.contains(QLatin1String("B"), Qt::CaseInsensitive)
       
   590         || typeParameters.contains(QLatin1String("BASE64"), Qt::CaseInsensitive)
       
   591         || typeParameters.contains(QLatin1String("B"), Qt::CaseInsensitive)) {
       
   592         value = QByteArray::fromBase64(value);
       
   593         // Remove the encoding parameter as the value is now decoded
       
   594         property.removeParameters(QLatin1String("ENCODING"));
       
   595         return true;
       
   596     }
       
   597     return false;
       
   598 }
       
   599 
       
   600 /*!
       
   601  * Decodes \a value, after working out what charset it is in using the context of \a property and
       
   602  * returns it.  The codec used to decode is returned in \a codec.
       
   603  */
       
   604 QString QVersitReaderPrivate::decodeCharset(const QByteArray& value,
       
   605                                             QVersitProperty& property,
       
   606                                             QTextCodec* defaultCodec,
       
   607                                             QTextCodec** codec) const
       
   608 {
       
   609     const QString charset(QLatin1String("CHARSET"));
       
   610     if (property.parameters().contains(charset)) {
       
   611         QString charsetValue = *property.parameters().find(charset);
       
   612         property.removeParameters(charset);
       
   613         *codec = QTextCodec::codecForName(charsetValue.toAscii());
       
   614         if (*codec != NULL) {
       
   615             return (*codec)->toUnicode(value);
       
   616         } else {
       
   617             *codec = defaultCodec;
       
   618             return defaultCodec->toUnicode(value);
       
   619         }
       
   620     }
       
   621     *codec = defaultCodec;
       
   622     return defaultCodec->toUnicode(value);
       
   623 }
       
   624 
       
   625 /*!
       
   626  * Decodes Quoted-Printable encoded (RFC 1521) characters in /a text.
       
   627  */
       
   628 void QVersitReaderPrivate::decodeQuotedPrintable(QByteArray& text) const
       
   629 {
       
   630     for (int i=0; i < text.length(); i++) {
       
   631         char current = text.at(i);
       
   632         if (current == '=' && i+2 < text.length()) {
       
   633             char next = text.at(i+1);
       
   634             char nextAfterNext = text.at(i+2);
       
   635             if (((next >= 'a' && next <= 'f') ||
       
   636                  (next >= 'A' && next <= 'F') ||
       
   637                  (next >= '0' && next <= '9')) &&
       
   638                 ((nextAfterNext >= 'a' && nextAfterNext <= 'f') ||
       
   639                  (nextAfterNext >= 'A' && nextAfterNext <= 'F') ||
       
   640                  (nextAfterNext >= '0' && nextAfterNext <= '9'))) {
       
   641                 bool ok;
       
   642                 char decodedChar(text.mid(i+1, 2).toInt(&ok,16));
       
   643                 if (ok) {
       
   644                     text[i] = decodedChar;
       
   645                     text.remove(i+1, 2);
       
   646                 }
       
   647             } else if (next == '\r' && nextAfterNext == '\n') {
       
   648                 // Newlines can still be found here if they are encoded in a non-default charset.
       
   649                 text.remove(i, 3);
       
   650             }
       
   651         }
       
   652     }
       
   653 }
       
   654 
       
   655 /*!
       
   656  * Extracts the groups and the name of the property using \a codec to determine the delimiters
       
   657  *
       
   658  * On entry, \a line should select a whole line.
       
   659  * On exit, \a line will be updated to point after the groups and name.
       
   660  */
       
   661 QPair<QStringList,QString>QVersitReaderPrivate::extractPropertyGroupsAndName(
       
   662         VersitCursor& line, QTextCodec *codec) const
       
   663 {
       
   664     const QByteArray semicolon = VersitUtils::encode(';', codec);
       
   665     const QByteArray colon = VersitUtils::encode(':', codec);
       
   666     const QByteArray backslash = VersitUtils::encode('\\', codec);
       
   667     QPair<QStringList,QString> groupsAndName;
       
   668     int length = 0;
       
   669     Q_ASSERT(line.data.size() >= line.position);
       
   670 
       
   671     int separatorLength = semicolon.length();
       
   672     for (int i = line.position; i < line.selection - separatorLength + 1; i++) {
       
   673         if ((containsAt(line.data, semicolon, i)
       
   674                 && !containsAt(line.data, backslash, i-separatorLength))
       
   675             || containsAt(line.data, colon, i)) {
       
   676             length = i - line.position;
       
   677             break;
       
   678         }
       
   679     }
       
   680     if (length > 0) {
       
   681         QString trimmedGroupsAndName =
       
   682                 codec->toUnicode(line.data.mid(line.position, length)).trimmed();
       
   683         QStringList parts = trimmedGroupsAndName.split(QLatin1Char('.'));
       
   684         if (parts.count() > 1) {
       
   685             groupsAndName.second = parts.takeLast();
       
   686             groupsAndName.first = parts;
       
   687         } else {
       
   688             groupsAndName.second = trimmedGroupsAndName;
       
   689         }
       
   690         line.setPosition(length + line.position);
       
   691     }
       
   692 
       
   693     return groupsAndName;
       
   694 }
       
   695 
       
   696 /*!
       
   697  * Extracts the value of the property.
       
   698  * Returns an empty string if the value was not found.
       
   699  *
       
   700  * On entry \a line should point to the value anyway.
       
   701  * On exit \a line should point to newline after the value
       
   702  */
       
   703 QByteArray QVersitReaderPrivate::extractPropertyValue(VersitCursor& line) const
       
   704 {
       
   705     QByteArray value = line.data.mid(line.position, line.selection - line.position);
       
   706 
       
   707     /* Now advance the cursor in all cases. */
       
   708     line.position = line.selection;
       
   709     return value;
       
   710 }
       
   711 
       
   712 /*!
       
   713  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
       
   714  * The parameters without names are added as "TYPE" parameters.
       
   715  *
       
   716  * On entry \a line should contain the entire line.
       
   717  * On exit, line will be updated to point to the start of the value.
       
   718  */
       
   719 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard21PropertyParams(
       
   720         VersitCursor& line, QTextCodec *codec) const
       
   721 {
       
   722     QMultiHash<QString,QString> result;
       
   723     QList<QByteArray> paramList = extractParams(line, codec);
       
   724     while (!paramList.isEmpty()) {
       
   725         QByteArray param = paramList.takeLast();
       
   726         QString name = paramName(param, codec);
       
   727         QString value = paramValue(param, codec);
       
   728         result.insert(name,value);
       
   729     }
       
   730 
       
   731     return result;
       
   732 }
       
   733 
       
   734 /*!
       
   735  * Extracts the property parameters as a QMultiHash using \a codec to determine the delimiters.
       
   736  * The parameters without names are added as "TYPE" parameters.
       
   737  */
       
   738 QMultiHash<QString,QString> QVersitReaderPrivate::extractVCard30PropertyParams(
       
   739         VersitCursor& line, QTextCodec *codec) const
       
   740 {
       
   741     QMultiHash<QString,QString> result;
       
   742     QList<QByteArray> paramList = extractParams(line, codec);
       
   743     while (!paramList.isEmpty()) {
       
   744         QByteArray param = paramList.takeLast();
       
   745         QString name(paramName(param, codec));
       
   746         removeBackSlashEscaping(name);
       
   747         QString values = paramValue(param, codec);
       
   748         QStringList valueList = splitValue(values, QLatin1Char(','), QString::SkipEmptyParts, true);
       
   749         foreach (QString value, valueList) {
       
   750             removeBackSlashEscaping(value);
       
   751             result.insert(name, value);
       
   752         }
       
   753     }
       
   754     return result;
       
   755 }
       
   756 
       
   757 
       
   758 /*!
       
   759  * Extracts the parameters as delimited by semicolons using \a codec to determine the delimiters.
       
   760  *
       
   761  * On entry \a line should point to the start of the parameter section (past the name).
       
   762  * On exit, \a line will be updated to point to the start of the value.
       
   763  */
       
   764 QList<QByteArray> QVersitReaderPrivate::extractParams(VersitCursor& line, QTextCodec *codec) const
       
   765 {
       
   766     const QByteArray colon = VersitUtils::encode(':', codec);
       
   767     QList<QByteArray> params;
       
   768 
       
   769     /* find the end of the name&params */
       
   770     int colonIndex = line.data.indexOf(colon, line.position);
       
   771     if (colonIndex > line.position && colonIndex < line.selection) {
       
   772         QByteArray nameAndParamsString = line.data.mid(line.position, colonIndex - line.position);
       
   773         params = extractParts(nameAndParamsString, VersitUtils::encode(';', codec), codec);
       
   774 
       
   775         /* Update line */
       
   776         line.setPosition(colonIndex + colon.length());
       
   777     } else if (colonIndex == line.position) {
       
   778         // No parameters.. advance past it
       
   779         line.setPosition(line.position + colon.length());
       
   780     }
       
   781 
       
   782     return params;
       
   783 }
       
   784 
       
   785 /*!
       
   786  * Extracts the parts separated by separator discarding the separators escaped with a backslash
       
   787  * encoded with \a codec
       
   788  */
       
   789 QList<QByteArray> QVersitReaderPrivate::extractParts(
       
   790         const QByteArray& text, const QByteArray& separator, QTextCodec* codec) const
       
   791 {
       
   792     QList<QByteArray> parts;
       
   793     int partStartIndex = 0;
       
   794     int textLength = text.length();
       
   795     int separatorLength = separator.length();
       
   796     QByteArray backslash = VersitUtils::encode('\\', codec);
       
   797     int backslashLength = backslash.length();
       
   798 
       
   799     for (int i=0; i < textLength-separatorLength+1; i++) {
       
   800         if (containsAt(text, separator, i)
       
   801             && (i < backslashLength
       
   802                 || !containsAt(text, backslash, i-backslashLength))) {
       
   803             int length = i-partStartIndex;
       
   804             QByteArray part = extractPart(text,partStartIndex,length);
       
   805             if (part.length() > 0)
       
   806                 parts.append(part);
       
   807             partStartIndex = i+separatorLength;
       
   808         }
       
   809     }
       
   810 
       
   811     // Add the last or only part
       
   812     QByteArray part = extractPart(text,partStartIndex);
       
   813     if (part.length() > 0)
       
   814         parts.append(part);
       
   815     return parts;
       
   816 }
       
   817 
       
   818 /*!
       
   819  * Extracts a substring limited by /a startPosition and /a length.
       
   820  */
       
   821 QByteArray QVersitReaderPrivate::extractPart(
       
   822         const QByteArray& text, int startPosition, int length) const
       
   823 {
       
   824     QByteArray part;
       
   825     if (startPosition >= 0)
       
   826         part = text.mid(startPosition,length).trimmed();
       
   827     return part;
       
   828 }
       
   829 
       
   830 /*!
       
   831  * Extracts the name of the parameter using \a codec to determine the delimiters.
       
   832  * No name is interpreted as an implicit "TYPE".
       
   833  */
       
   834 QString QVersitReaderPrivate::paramName(const QByteArray& parameter, QTextCodec* codec) const
       
   835 {
       
   836      if (parameter.trimmed().length() == 0)
       
   837          return QString();
       
   838      QByteArray equals = VersitUtils::encode('=', codec);
       
   839      int equalsIndex = parameter.indexOf(equals);
       
   840      if (equalsIndex > 0) {
       
   841          return codec->toUnicode(parameter.left(equalsIndex)).trimmed();
       
   842      }
       
   843 
       
   844      return QLatin1String("TYPE");
       
   845 }
       
   846 
       
   847 /*!
       
   848  * Extracts the value of the parameter using \a codec to determine the delimiters
       
   849  */
       
   850 QString QVersitReaderPrivate::paramValue(const QByteArray& parameter, QTextCodec* codec) const
       
   851 {
       
   852     QByteArray value(parameter);
       
   853     QByteArray equals = VersitUtils::encode('=', codec);
       
   854     int equalsIndex = parameter.indexOf(equals);
       
   855     if (equalsIndex > 0) {
       
   856         int valueLength = parameter.length() - (equalsIndex + equals.length());
       
   857         value = parameter.right(valueLength).trimmed();
       
   858     }
       
   859 
       
   860     return codec->toUnicode(value);
       
   861 }
       
   862 
       
   863 /*!
       
   864  * Returns true if and only if \a text contains \a ba at \a index
       
   865  *
       
   866  * On entry, index must be >= 0
       
   867  */
       
   868 bool QVersitReaderPrivate::containsAt(const QByteArray& text, const QByteArray& match, int index)
       
   869 {
       
   870     int n = match.length();
       
   871     if (text.length() - index < n)
       
   872         return false;
       
   873     const char* textData = text.constData();
       
   874     const char* matchData = match.constData();
       
   875     return memcmp(textData+index, matchData, n) == 0;
       
   876 }
       
   877 
       
   878 /*!
       
   879  * If the \a type and the \a property's name is known to contain a structured value, \a property's
       
   880  * value is split according to the type of structuring (compound vs. list) it is known to have.
       
   881  * Returns true if and only if such a split happened (ie. the property value holds a QStringList on
       
   882  * exit).
       
   883  */
       
   884 bool QVersitReaderPrivate::splitStructuredValue(
       
   885         QVersitProperty& property,
       
   886         bool hasEscapedBackslashes) const
       
   887 {
       
   888     QVariant variant = property.variantValue();
       
   889     if (property.valueType() == QVersitProperty::CompoundType) {
       
   890         variant.setValue(splitValue(variant.toString(), QLatin1Char(';'),
       
   891                                     QString::KeepEmptyParts, hasEscapedBackslashes));
       
   892         property.setValue(variant);
       
   893         return true;
       
   894     } else if (property.valueType() == QVersitProperty::ListType) {
       
   895         variant.setValue(splitValue(variant.toString(), QLatin1Char(','),
       
   896                                     QString::SkipEmptyParts, hasEscapedBackslashes));
       
   897         property.setValue(variant);
       
   898         return true;
       
   899     }
       
   900     return false;
       
   901 }
       
   902 
       
   903 /*!
       
   904  * Splits the \a string into substrings wherever \a sep occurs.
       
   905  * If \a hasEscapedBackslashes is false, then a \a sep preceded by a backslash is not considered
       
   906  * a split point (but the backslash is removed).
       
   907  * If \a hasEscapedBackslashes is true, then a \a sep preceded by an odd number of backslashes is
       
   908  * not considered a split point (but one backslash is removed).
       
   909  */
       
   910 QStringList QVersitReaderPrivate::splitValue(const QString& string,
       
   911                                              const QChar& sep,
       
   912                                              QString::SplitBehavior behaviour,
       
   913                                              bool hasEscapedBackslashes)
       
   914 {
       
   915     QStringList list;
       
   916     bool isEscaped = false; // is the current character escaped
       
   917     int segmentStartIndex = 0;
       
   918     QString segment;
       
   919     for (int i = 0; i < string.length(); i++) {
       
   920         if (string.at(i) == QLatin1Char('\\')) {
       
   921             if (hasEscapedBackslashes)
       
   922                 isEscaped = !isEscaped; // two consecutive backslashes make isEscaped false
       
   923             else
       
   924                 isEscaped = true;
       
   925         } else if (string.at(i) == sep) {
       
   926             if (isEscaped) {
       
   927                 // we see an escaped separator - remove the backslash
       
   928                 segment += string.midRef(segmentStartIndex, i-segmentStartIndex-1);
       
   929                 segment += sep;
       
   930             } else {
       
   931                 // we see a separator
       
   932                 segment += string.midRef(segmentStartIndex, i - segmentStartIndex);
       
   933                 if (behaviour == QString::KeepEmptyParts || !segment.isEmpty())
       
   934                     list.append(segment);
       
   935                 segment.clear();
       
   936             }
       
   937             segmentStartIndex = i+1;
       
   938             isEscaped = false;
       
   939         } else { // normal character - keep going
       
   940             isEscaped = false;
       
   941         }
       
   942     }
       
   943     // The rest of the string after the last sep.
       
   944     segment += string.midRef(segmentStartIndex);
       
   945     if (behaviour == QString::KeepEmptyParts || !segment.isEmpty())
       
   946         list.append(segment);
       
   947     return list;
       
   948 }
       
   949 
       
   950 /*!
       
   951  * Removes backslash escaping for line breaks (CRLFs), colons, semicolons, backslashes and commas
       
   952  * according to RFC 2426.  This is called on parameter names and values and property values.
       
   953  * Colons ARE unescaped because the text of RFC2426 suggests that they should be.
       
   954  */
       
   955 void QVersitReaderPrivate::removeBackSlashEscaping(QString& text)
       
   956 {
       
   957     if (!(text.startsWith(QLatin1Char('"')) && text.endsWith(QLatin1Char('"')))) {
       
   958         /* replaces \; with ;
       
   959                     \, with ,
       
   960                     \: with :
       
   961                     \\ with \
       
   962          */
       
   963         text.replace(QRegExp(QLatin1String("\\\\([;,:\\\\])")), QLatin1String("\\1"));
       
   964         // replaces \n with a CRLF
       
   965         text.replace(QLatin1String("\\n"), QLatin1String("\r\n"), Qt::CaseInsensitive);
       
   966     }
       
   967 }
       
   968 
       
   969 
       
   970 #include "moc_qversitreader_p.cpp"