1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtVersit module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 or version 3 as published by the Free |
20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
22 | ** following information to ensure the GNU Lesser General Public License |
23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
25 | ** |
26 | ** As a special exception, The Qt Company gives you certain additional |
27 | ** rights. These rights are described in The Qt Company LGPL Exception |
28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
29 | ** |
30 | ** $QT_END_LICENSE$ |
31 | ** |
32 | ****************************************************************************/ |
33 | |
34 | #ifndef QVERSITREADER_P_H |
35 | #define QVERSITREADER_P_H |
36 | |
37 | // |
38 | // W A R N I N G |
39 | // ------------- |
40 | // |
41 | // This file is not part of the Qt API. It exists purely as an |
42 | // implementation detail. This header file may change from version to |
43 | // version without notice, or even be removed. |
44 | // |
45 | // We mean it. |
46 | // |
47 | |
48 | #include <QtCore/qbytearray.h> |
49 | #include <QtCore/qbytearraymatcher.h> |
50 | #include <QtCore/qhash.h> |
51 | #include <QtCore/qlist.h> |
52 | #include <QtCore/qmutex.h> |
53 | #include <QtCore/qpair.h> |
54 | #include <QtCore/qpointer.h> |
55 | #include <QtCore/qscopedpointer.h> |
56 | #include <QtCore/qstack.h> |
57 | #include <QtCore/qthread.h> |
58 | |
59 | #include <QtVersit/qversitreader.h> |
60 | #include <QtVersit/qversitdocument.h> |
61 | #include <QtVersit/qversitproperty.h> |
62 | |
63 | QT_FORWARD_DECLARE_CLASS(QBuffer) |
64 | QT_FORWARD_DECLARE_CLASS(QIODevice) |
65 | QT_FORWARD_DECLARE_CLASS(QTextCodec) |
66 | |
67 | QT_BEGIN_NAMESPACE_VERSIT |
68 | |
69 | // The maximum number of bytes allowed to stay in memory after being read. The smaller this is, |
70 | // the more time spent moving bytes around. The larger it is, the more memory is wasted. |
71 | static const int MAX_OLD_BYTES_TO_KEEP = 8192; |
72 | |
73 | /* |
74 | * An LByteArray has a subset of QByteArray's interface, plus an efficient chopLeft function |
75 | * |
76 | * It stores a QByteArray internally, plus a marker of where it starts and where it ends. |
77 | */ |
78 | class LByteArray |
79 | { |
80 | public: |
81 | LByteArray() : mStart(0), mEnd(0) {} |
82 | explicit LByteArray(const QByteArray& d) :mData(d), mStart(0), mEnd(d.size()) {} |
83 | LByteArray(const QByteArray& d, int start, int end) :mData(d), mStart(start), mEnd(end) {} |
84 | bool isEmpty() const { |
85 | return mEnd <= mStart; |
86 | } |
87 | char at(int i) const { |
88 | return mData.at(i: mStart + i); |
89 | } |
90 | QByteArray toByteArray() const { |
91 | return mData.mid(index: mStart, len: mEnd-mStart); |
92 | } |
93 | /* Removes \a n bytes from the start of the QByteArray. */ |
94 | void chopLeft(int n) { |
95 | Q_ASSERT(size() >= n && n >= 0); |
96 | mStart += n; |
97 | } |
98 | QByteArray left(int n) { |
99 | Q_ASSERT(size() >= n && n >= 0); |
100 | return mData.mid(index: mStart, len: n); |
101 | } |
102 | int indexOf(const QByteArray& needle) { |
103 | int index = mData.indexOf(a: needle, from: mStart) - mStart; |
104 | if (index < size()) |
105 | return index; |
106 | return -1; |
107 | } |
108 | int size() const { |
109 | return mEnd - mStart; |
110 | } |
111 | const char* constData() const { |
112 | return mData.constData() + mStart; |
113 | } |
114 | bool contains(const QByteArray& ba) const { |
115 | int i = mData.indexOf(a: ba, from: mStart); |
116 | return i > 0 && i <= mEnd - ba.length(); |
117 | } |
118 | bool endsWith(const QByteArray& ba) const { |
119 | // Loop backwards from ba and from mData (starting from index mEnd) |
120 | if (ba.size() > size()) |
121 | return false; |
122 | return memcmp(s1: mData.constData()+mEnd-ba.size(), s2: ba.constData(), n: ba.size()) == 0; |
123 | } |
124 | LByteArray& operator=(const QByteArray& ba) { |
125 | mData = ba; |
126 | mStart = 0; |
127 | mEnd = mData.size(); |
128 | return *this; |
129 | } |
130 | bool operator==(const QByteArray& ba) { |
131 | return toByteArray() == ba; |
132 | } |
133 | bool operator!=(const QByteArray& ba) { |
134 | return toByteArray() != ba; |
135 | } |
136 | |
137 | private: |
138 | /* Clears the memory of bytes before the start marker */ |
139 | void dropOldData() { |
140 | if (mStart > MAX_OLD_BYTES_TO_KEEP && mEnd >= mStart) { |
141 | mData.remove(index: 0, len: mStart); |
142 | mEnd -= mStart; |
143 | mStart = 0; |
144 | } |
145 | } |
146 | void setBounds(int start, int end) { |
147 | mStart = start; |
148 | mEnd = end; |
149 | } |
150 | QByteArray mData; |
151 | int mStart; |
152 | int mEnd; |
153 | friend class LineReader; |
154 | }; |
155 | |
156 | class Q_VERSIT_EXPORT LineReader |
157 | { |
158 | public: |
159 | LineReader(QIODevice* device, QTextCodec* codec); |
160 | LineReader(QIODevice* device); |
161 | LineReader(QIODevice* device, QTextCodec* codec, int chunkSize); |
162 | void init(); |
163 | void pushLine(const QByteArray& line); |
164 | int odometer() const; |
165 | bool atEnd() const; |
166 | QTextCodec* codec() const; |
167 | bool isCodecCertain() const; |
168 | bool isCodecUtf8Compatible() const; |
169 | void setCodecUtf8Incompatible(); |
170 | LByteArray readLine(); |
171 | |
172 | private: |
173 | void readOneLine(LByteArray* cursor); |
174 | bool tryReadLine(LByteArray* cursor, bool atEnd); |
175 | |
176 | QIODevice* const mDevice; |
177 | QTextCodec* mCodec; |
178 | bool mIsCodecCertain; |
179 | bool mIsCodecUtf8Compatible; |
180 | int mChunkSize; // How many bytes to read in one go. |
181 | QList<QByteArrayMatcher> mCrlfList; |
182 | QStack<QByteArray> mPushedLines; // Stores a lines that has been "pushed" in front by pushLine |
183 | LByteArray mBuffer; |
184 | int mOdometer; |
185 | int mSearchFrom; |
186 | }; |
187 | |
188 | class Q_VERSIT_EXPORT QVersitReaderPrivate : public QThread |
189 | { |
190 | Q_OBJECT |
191 | |
192 | public: // Constructors and destructor |
193 | QVersitReaderPrivate(); |
194 | ~QVersitReaderPrivate(); |
195 | |
196 | static QHash<QPair<QVersitDocument::VersitType,QString>, QVersitProperty::ValueType>* |
197 | valueTypeMap(); |
198 | void init(QVersitReader* reader); |
199 | |
200 | signals: |
201 | void stateChanged(QVersitReader::State state); |
202 | void resultsAvailable(); |
203 | |
204 | protected: // From QThread |
205 | void run(); |
206 | |
207 | public: // New functions |
208 | void read(); |
209 | |
210 | // mutexed getters and setters. |
211 | void setState(QVersitReader::State); |
212 | QVersitReader::State state() const; |
213 | void setError(QVersitReader::Error); |
214 | QVersitReader::Error error() const; |
215 | void setCanceling(bool cancelling); |
216 | bool isCanceling(); |
217 | |
218 | bool parseVersitDocument(LineReader* lineReader, QVersitDocument* document); |
219 | bool parseVersitDocumentBody(LineReader* lineReader, QVersitDocument* document); |
220 | |
221 | QVersitProperty parseNextVersitProperty( |
222 | QVersitDocument::VersitType versitType, |
223 | LineReader* lineReader); |
224 | |
225 | void parseVCard21Property( |
226 | LByteArray* text, |
227 | QVersitProperty* property, |
228 | LineReader* lineReader); |
229 | |
230 | void parseVCard30Property( |
231 | QVersitDocument::VersitType versitType, |
232 | LByteArray* text, |
233 | QVersitProperty* property, |
234 | LineReader* lineReader); |
235 | |
236 | bool setVersionFromProperty( |
237 | QVersitDocument* document, |
238 | const QVersitProperty& property) const; |
239 | |
240 | bool unencode( |
241 | QByteArray* value, |
242 | QVersitProperty* property, |
243 | LineReader* lineReader) const; |
244 | |
245 | QString decodeCharset( |
246 | const QByteArray& value, |
247 | QVersitProperty* property, |
248 | LineReader* lineReader, |
249 | QTextCodec** codec) const; |
250 | |
251 | void decodeQuotedPrintable(QByteArray* text) const; |
252 | |
253 | |
254 | /* These functions operate on a cursor describing a single line */ |
255 | QPair<QStringList,QString> extractPropertyGroupsAndName(LByteArray* line, QTextCodec* codec) |
256 | const; |
257 | QMultiHash<QString,QString> (LByteArray* line, QTextCodec* codec) |
258 | const; |
259 | QMultiHash<QString,QString> (LByteArray* line, QTextCodec* codec) |
260 | const; |
261 | |
262 | // "Private" functions |
263 | QList<QByteArray> (LByteArray* line, QTextCodec *codec) const; |
264 | QList<QByteArray> (const QByteArray& text, const QByteArray& separator, |
265 | QTextCodec *codec) const; |
266 | QByteArray (const QByteArray& text, int startPosition, int length=-1) const; |
267 | QString paramName(const QByteArray& parameter, QTextCodec* codec) const; |
268 | QString paramValue(const QByteArray& parameter, QTextCodec* codec) const; |
269 | template <class T> static bool containsAt(const T& text, const QByteArray& ba, int index); |
270 | bool splitStructuredValue(QVersitProperty* property, |
271 | bool hasEscapedBackslashes) const; |
272 | static QStringList splitValue(const QString& string, |
273 | const QChar& sep, |
274 | QString::SplitBehavior behaviour, |
275 | bool hasEscapedBackslashes); |
276 | static void removeBackSlashEscaping(QString* text); |
277 | |
278 | // Data |
279 | public: |
280 | QPointer<QIODevice> mIoDevice; |
281 | QScopedPointer<QBuffer> mInputBytes; // Holds the data set by setData() |
282 | QList<QVersitDocument> mVersitDocuments; |
283 | int mDocumentNestingLevel; // Depth in parsing nested Versit documents |
284 | QTextCodec* mDefaultCodec; |
285 | QVersitReader::State mState; |
286 | QVersitReader::Error mError; |
287 | bool mIsCanceling; |
288 | mutable QMutex mMutex; |
289 | |
290 | private: |
291 | /* key is the document type and property name, value is the type of property it is. |
292 | If there is no entry, assume it is a PlainType */ |
293 | static QHash<QPair<QVersitDocument::VersitType,QString>, QVersitProperty::ValueType>* mValueTypeMap; |
294 | }; |
295 | |
296 | QT_END_NAMESPACE_VERSIT |
297 | |
298 | #endif // QVERSITREADER_P_H |
299 | |