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