1 #include "http_request.hpp"
2 #include "http_file_source.hpp"
3 
4 #include <mbgl/storage/response.hpp>
5 #include <mbgl/util/chrono.hpp>
6 #include <mbgl/util/optional.hpp>
7 #include <mbgl/util/http_header.hpp>
8 #include <mbgl/util/string.hpp>
9 #include <mbgl/util/version.hpp>
10 
11 #include <QByteArray>
12 #include <QNetworkReply>
13 #include <QPair>
14 
15 namespace mbgl {
16 
HTTPRequest(HTTPFileSource::Impl * context,const Resource & resource,FileSource::Callback callback)17 HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context, const Resource& resource, FileSource::Callback callback)
18     : m_context(context)
19     , m_resource(resource)
20     , m_callback(callback)
21 {
22     m_context->request(this);
23 }
24 
~HTTPRequest()25 HTTPRequest::~HTTPRequest()
26 {
27     if (!m_handled) {
28         m_context->cancel(this);
29     }
30 }
31 
requestUrl() const32 QUrl HTTPRequest::requestUrl() const
33 {
34     return QUrl::fromPercentEncoding(QByteArray(m_resource.url.data(), m_resource.url.size()));
35 }
36 
networkRequest() const37 QNetworkRequest HTTPRequest::networkRequest() const
38 {
39     QNetworkRequest req = QNetworkRequest(requestUrl());
40     req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
41 
42     static const QByteArray agent = QString("MapboxGL/%1 (Qt %2)").arg(version::revision).arg(QT_VERSION_STR).toLatin1();
43     req.setRawHeader("User-Agent", agent);
44 
45     if (m_resource.priorEtag) {
46         const auto etag = m_resource.priorEtag;
47         req.setRawHeader("If-None-Match", QByteArray(etag->data(), etag->size()));
48     } else if (m_resource.priorModified) {
49         req.setRawHeader("If-Modified-Since", util::rfc1123(*m_resource.priorModified).c_str());
50     }
51 
52     return req;
53 }
54 
handleNetworkReply(QNetworkReply * reply,const QByteArray & data)55 void HTTPRequest::handleNetworkReply(QNetworkReply *reply, const QByteArray& data)
56 {
57     m_handled = true;
58 
59     // Calling `callback` may result in deleting `this`.
60     // Copy data to temporaries first.
61     auto callback = m_callback;
62     mbgl::Response response;
63 
64     using Error = Response::Error;
65 
66     // Handle non-HTTP errors (i.e. like connection).
67     if (reply->error() && reply->error() < 100) {
68         response.error = std::make_unique<Error>(
69             Error::Reason::Connection, reply->errorString().toStdString());
70         callback(response);
71         return;
72     }
73 
74     QPair<QByteArray, QByteArray> line;
75     optional<std::string> retryAfter;
76     optional<std::string> xRateLimitReset;
77     foreach(line, reply->rawHeaderPairs()) {
78         QString header = QString(line.first).toLower();
79 
80         if (header == "last-modified") {
81             response.modified = util::parseTimestamp(line.second.constData());
82         } else if (header == "etag") {
83             response.etag = std::string(line.second.constData(), line.second.size());
84         } else if (header == "cache-control") {
85             const auto cc = http::CacheControl::parse(line.second.constData());
86             response.expires = cc.toTimePoint();
87             response.mustRevalidate = cc.mustRevalidate;
88         } else if (header == "expires") {
89             response.expires = util::parseTimestamp(line.second.constData());
90         } else if (header == "retry-after") {
91             retryAfter = std::string(line.second.constData(), line.second.size());
92         } else if (header == "x-rate-limit-reset") {
93             xRateLimitReset = std::string(line.second.constData(), line.second.size());
94         }
95     }
96 
97     if (reply->url().scheme() == QStringLiteral("data")) {
98         if (data.isEmpty()) {
99             response.data = std::make_shared<std::string>();
100         } else {
101             response.data = std::make_shared<std::string>(data.constData(), data.size());
102         }
103         callback(response);
104         return;
105     }
106 
107     int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
108 
109     switch(responseCode) {
110     case 200: {
111         if (data.isEmpty()) {
112             response.data = std::make_shared<std::string>();
113         } else {
114             response.data = std::make_shared<std::string>(data.constData(), data.size());
115         }
116         break;
117     }
118     case 204:
119         response.noContent = true;
120         break;
121     case 304:
122         response.notModified = true;
123         break;
124     case 404: {
125         if (m_resource.kind == Resource::Kind::Tile) {
126             response.noContent = true;
127         } else {
128             response.error = std::make_unique<Error>(
129                 Error::Reason::NotFound, "HTTP status code 404");
130         }
131         break;
132     }
133     case 429:
134         response.error = std::make_unique<Error>(
135                 Error::Reason::RateLimit, "HTTP status code 429",
136                 http::parseRetryHeaders(retryAfter, xRateLimitReset));
137         break;
138     default:
139         Response::Error::Reason reason = (responseCode >= 500 && responseCode < 600) ?
140             Error::Reason::Server : Error::Reason::Other;
141 
142         response.error = std::make_unique<Error>(
143             reason, "HTTP status code " + util::toString(responseCode));
144     }
145 
146     callback(response);
147 }
148 
149 } // namespace mbgl
150