QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
19#include "qgslogger.h"
21
22#include <QNetworkReply>
23#include <QObject>
24#include <QRegularExpression>
25#include <QString>
26#include <QStringList>
27
29 : mReply( reply )
30 , mValid( false )
31{
32 if ( !mReply ) return;
33
34 // Content type examples:
35 // multipart/mixed; boundary=wcs
36 // multipart/mixed; boundary="wcs"\n
37 if ( !isMultipart( mReply ) )
38 {
39 // reply is not multipart, copy body and headers
40 QMap<QByteArray, QByteArray> headers;
41 const auto constRawHeaderList = mReply->rawHeaderList();
42 for ( const QByteArray &h : constRawHeaderList )
43 {
44 headers.insert( h, mReply->rawHeader( h ) );
45 }
46 mHeaders.append( headers );
47 mBodies.append( mReply->readAll() );
48 }
49 else // multipart
50 {
51 const QString contentType = mReply->header( QNetworkRequest::ContentTypeHeader ).toString();
52 QgsDebugMsgLevel( "contentType: " + contentType, 2 );
53
54 const thread_local QRegularExpression re( ".*boundary=\"?([^\"]+)\"?\\s?", QRegularExpression::CaseInsensitiveOption );
55 const QRegularExpressionMatch match = re.match( contentType );
56 if ( !( match.capturedStart( 0 ) == 0 ) )
57 {
58 mError = tr( "Cannot find boundary in multipart content type" );
59 return;
60 }
61
62 QString boundary = match.captured( 1 );
63 QgsDebugMsgLevel( QStringLiteral( "boundary = %1 size = %2" ).arg( boundary ).arg( boundary.size() ), 2 );
64 boundary = "--" + boundary;
65
66 // Lines should be terminated by CRLF ("\r\n") but any new line combination may appear
67 const QByteArray data = mReply->readAll();
68 int from, to;
69 from = data.indexOf( boundary.toLatin1(), 0 ) + boundary.length() + 1;
70 //QVector<QByteArray> partHeaders;
71 //QVector<QByteArray> partBodies;
72 while ( true )
73 {
74 // 'to' is not really 'to', but index of the next byte after the end of part
75 to = data.indexOf( boundary.toLatin1(), from );
76 if ( to < 0 )
77 {
78 QgsDebugMsgLevel( QStringLiteral( "No more boundaries, rest size = %1" ).arg( data.size() - from - 1 ), 2 );
79 // It may be end, last boundary is followed by '--'
80 if ( data.size() - from - 1 == 2 && QString( data.mid( from, 2 ) ) == QLatin1String( "--" ) ) // end
81 {
82 break;
83 }
84
85 // It may happen that boundary is missing at the end (GeoServer)
86 // in that case, take everything to the end
87 if ( data.size() - from > 1 )
88 {
89 to = data.size(); // out of range OK
90 }
91 else
92 {
93 break;
94 }
95 }
96 QgsDebugMsgLevel( QStringLiteral( "part %1 - %2" ).arg( from ).arg( to ), 2 );
97 QByteArray part = data.mid( from, to - from );
98 // Remove possible new line from beginning
99 while ( !part.isEmpty() && ( part.at( 0 ) == '\r' || part.at( 0 ) == '\n' ) )
100 {
101 part.remove( 0, 1 );
102 }
103 // Split header and data (find empty new line)
104 // New lines should be CRLF, but we support also CRLFCRLF, LFLF to find empty line
105 int pos = 0; // body start
106 while ( pos < part.size() - 1 )
107 {
108 if ( part.at( pos ) == '\n' && ( part.at( pos + 1 ) == '\n' || part.at( pos + 1 ) == '\r' ) )
109 {
110 if ( part.at( pos + 1 ) == '\r' ) pos++;
111 pos += 2;
112 break;
113 }
114 pos++;
115 }
116 // parse headers
117 RawHeaderMap headersMap;
118 const QByteArray headers = part.left( pos );
119 QgsDebugMsgLevel( "headers:\n" + headers, 2 );
120
121 const QStringList headerRows = QString( headers ).split( QRegularExpression( "[\n\r]+" ) );
122 const auto constHeaderRows = headerRows;
123 for ( const QString &row : constHeaderRows )
124 {
125 QgsDebugMsgLevel( "row = " + row, 2 );
126 const QStringList kv = row.split( QStringLiteral( ": " ) );
127 headersMap.insert( kv.value( 0 ).toLatin1(), kv.value( 1 ).toLatin1() );
128 }
129 mHeaders.append( headersMap );
130
131 mBodies.append( part.mid( pos ) );
132
133 from = to + boundary.length();
134 }
135 }
136 mValid = true;
137}
138
139bool QgsNetworkReplyParser::isMultipart( QNetworkReply *reply )
140{
141 if ( !reply ) return false;
142
143 const QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
144 QgsDebugMsgLevel( "contentType: " + contentType, 2 );
145
146 // Multipart content type examples:
147 // multipart/mixed; boundary=wcs
148 // multipart/mixed; boundary="wcs"\n
149 return contentType.startsWith( QLatin1String( "multipart/" ), Qt::CaseInsensitive );
150}
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:39