QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgsnetworkreplyparser.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsnetworkreplyparser.cpp - Multipart QNetworkReply parser
3 -------------------
4 begin : 4 January, 2013
5 copyright : (C) 2013 by Radim Blazek
6 email : radim dot blazek at gmail.com
7
8 ***************************************************************************/
9
10/***************************************************************************
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 ***************************************************************************/
18
20
21#include "qgslogger.h"
22
23#include <QNetworkReply>
24#include <QObject>
25#include <QRegularExpression>
26#include <QString>
27#include <QStringList>
28
29#include "moc_qgsnetworkreplyparser.cpp"
30
31using namespace Qt::StringLiterals;
32
34 : mReply( reply )
35{
36 if ( !mReply )
37 return;
38
39 // Content type examples:
40 // multipart/mixed; boundary=wcs
41 // multipart/mixed; boundary="wcs"\n
42 if ( !isMultipart( mReply ) )
43 {
44 // reply is not multipart, copy body and headers
45 QMap<QByteArray, QByteArray> headers;
46 const auto constRawHeaderList = mReply->rawHeaderList();
47 for ( const QByteArray &h : constRawHeaderList )
48 {
49 headers.insert( h, mReply->rawHeader( h ) );
50 }
51 mHeaders.append( headers );
52 mBodies.append( mReply->readAll() );
53 }
54 else // multipart
55 {
56 const QString contentType = mReply->header( QNetworkRequest::ContentTypeHeader ).toString();
57 QgsDebugMsgLevel( "contentType: " + contentType, 2 );
58
59 const thread_local QRegularExpression re( ".*boundary=\"?([^\"]+)\"?\\s?", QRegularExpression::CaseInsensitiveOption );
60 const QRegularExpressionMatch match = re.match( contentType );
61 if ( !( match.capturedStart( 0 ) == 0 ) )
62 {
63 mError = tr( "Cannot find boundary in multipart content type" );
64 return;
65 }
66
67 QString boundary = match.captured( 1 );
68 QgsDebugMsgLevel( u"boundary = %1 size = %2"_s.arg( boundary ).arg( boundary.size() ), 2 );
69 boundary = "--" + boundary;
70
71 // Lines should be terminated by CRLF ("\r\n") but any new line combination may appear
72 const QByteArray data = mReply->readAll();
73 int from, to;
74 from = data.indexOf( boundary.toLatin1(), 0 ) + boundary.length() + 1;
75 //QVector<QByteArray> partHeaders;
76 //QVector<QByteArray> partBodies;
77 while ( true )
78 {
79 // 'to' is not really 'to', but index of the next byte after the end of part
80 to = data.indexOf( boundary.toLatin1(), from );
81 if ( to < 0 )
82 {
83 QgsDebugMsgLevel( u"No more boundaries, rest size = %1"_s.arg( data.size() - from - 1 ), 2 );
84 // It may be end, last boundary is followed by '--'
85 if ( data.size() - from - 1 == 2 && QString( data.mid( from, 2 ) ) == "--"_L1 ) // end
86 {
87 break;
88 }
89
90 // It may happen that boundary is missing at the end (GeoServer)
91 // in that case, take everything to the end
92 if ( data.size() - from > 1 )
93 {
94 to = data.size(); // out of range OK
95 }
96 else
97 {
98 break;
99 }
100 }
101 QgsDebugMsgLevel( u"part %1 - %2"_s.arg( from ).arg( to ), 2 );
102 QByteArray part = data.mid( from, to - from );
103 // Remove possible new line from beginning
104 while ( !part.isEmpty() && ( part.at( 0 ) == '\r' || part.at( 0 ) == '\n' ) )
105 {
106 part.remove( 0, 1 );
107 }
108 // Split header and data (find empty new line)
109 // New lines should be CRLF, but we support also CRLFCRLF, LFLF to find empty line
110 int pos = 0; // body start
111 while ( pos < part.size() - 1 )
112 {
113 if ( part.at( pos ) == '\n' && ( part.at( pos + 1 ) == '\n' || part.at( pos + 1 ) == '\r' ) )
114 {
115 if ( part.at( pos + 1 ) == '\r' )
116 pos++;
117 pos += 2;
118 break;
119 }
120 pos++;
121 }
122 // parse headers
123 RawHeaderMap headersMap;
124 const QByteArray headers = part.left( pos );
125 QgsDebugMsgLevel( "headers:\n" + headers, 2 );
126
127 const QStringList headerRows = QString( headers ).split( QRegularExpression( "[\n\r]+" ) );
128 const auto constHeaderRows = headerRows;
129 for ( const QString &row : constHeaderRows )
130 {
131 QgsDebugMsgLevel( "row = " + row, 2 );
132 const QStringList kv = row.split( u": "_s );
133 headersMap.insert( kv.value( 0 ).toLatin1(), kv.value( 1 ).toLatin1() );
134 }
135 mHeaders.append( headersMap );
136
137 mBodies.append( part.mid( pos ) );
138
139 from = to + boundary.length();
140 }
141 }
142 mValid = true;
143}
144
145bool QgsNetworkReplyParser::isMultipart( QNetworkReply *reply )
146{
147 if ( !reply )
148 return false;
149
150 const QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
151 QgsDebugMsgLevel( "contentType: " + contentType, 2 );
152
153 // Multipart content type examples:
154 // multipart/mixed; boundary=wcs
155 // multipart/mixed; boundary="wcs"\n
156 return contentType.startsWith( "multipart/"_L1, Qt::CaseInsensitive );
157}
QMap< QByteArray, QByteArray > RawHeaderMap
QgsNetworkReplyParser(QNetworkReply *reply)
Constructor.
static bool isMultipart(QNetworkReply *reply)
Test if reply is multipart.
QList< RawHeaderMap > headers() const
Gets headers.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63