1 /******************************************************************************
2 *
3 * Project: WMS Client Driver
4 * Purpose: Implementation of Dataset and RasterBand classes for WMS
5 * and other similar services.
6 * Author: Adam Nowacki, nowak@xpam.de
7 *
8 ******************************************************************************
9 * Copyright (c) 2007, Adam Nowacki
10 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
11 * Copyright (c) 2016, Lucian Plesea
12 *
13 * Permission is hereby granted, free of charge, to any person obtaining a
14 * copy of this software and associated documentation files (the "Software"),
15 * to deal in the Software without restriction, including without limitation
16 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 * and/or sell copies of the Software, and to permit persons to whom the
18 * Software is furnished to do so, subject to the following conditions:
19 *
20 * The above copyright notice and this permission notice shall be included
21 * in all copies or substantial portions of the Software.
22 *
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 * DEALINGS IN THE SOFTWARE.
30 ****************************************************************************/
31
32 #include "wmsdriver.h"
33 #include <algorithm>
34
35 #if !CURL_AT_LEAST_VERSION(7,28,0)
36 // Needed for curl_multi_wait()
37 #error Need libcurl version 7.28.0 or newer
38 // 7.28 was released in Oct 2012
39 #endif
40
WriteFunc(void * buffer,size_t count,size_t nmemb,void * req)41 static size_t WriteFunc(void *buffer, size_t count, size_t nmemb, void *req) {
42 WMSHTTPRequest *psRequest = reinterpret_cast<WMSHTTPRequest *>(req);
43 size_t size = count * nmemb;
44
45 if (size == 0) return 0;
46
47 const size_t required_size = psRequest->nDataLen + size + 1;
48 if (required_size > psRequest->nDataAlloc) {
49 size_t new_size = required_size * 2;
50 if (new_size < 512) new_size = 512;
51 psRequest->nDataAlloc = new_size;
52 GByte * pabyNewData = reinterpret_cast<GByte *>(VSIRealloc(psRequest->pabyData, new_size));
53 if (pabyNewData == nullptr) {
54 VSIFree(psRequest->pabyData);
55 psRequest->pabyData = nullptr;
56 psRequest->Error.Printf("Out of memory allocating %u bytes for HTTP data buffer.",
57 static_cast<unsigned int>(new_size));
58 psRequest->nDataAlloc = 0;
59 psRequest->nDataLen = 0;
60 return 0;
61 }
62 psRequest->pabyData = pabyNewData;
63 }
64 memcpy(psRequest->pabyData + psRequest->nDataLen, buffer, size);
65 psRequest->nDataLen += size;
66 psRequest->pabyData[psRequest->nDataLen] = 0;
67 return nmemb;
68 }
69
70 // Process curl errors
ProcessCurlErrors(CURLMsg * msg,WMSHTTPRequest * pasRequest,int nRequestCount)71 static void ProcessCurlErrors(CURLMsg* msg, WMSHTTPRequest* pasRequest, int nRequestCount)
72 {
73 CPLAssert(msg != nullptr);
74 CPLAssert(msg->msg == CURLMSG_DONE);
75
76 // in case of local file error: update status code
77 if (msg->data.result == CURLE_FILE_COULDNT_READ_FILE) {
78 // identify current request
79 for (int current_req_i = 0; current_req_i < nRequestCount; ++current_req_i) {
80 WMSHTTPRequest* const psRequest = &pasRequest[current_req_i];
81 if (psRequest->m_curl_handle != msg->easy_handle)
82 continue;
83
84 // sanity check for local files
85 if (STARTS_WITH(psRequest->URL.c_str(), "file://")) {
86 psRequest->nStatus = 404;
87 break;
88 }
89 }
90 }
91 }
92
93 // Builds a curl request
WMSHTTPInitializeRequest(WMSHTTPRequest * psRequest)94 void WMSHTTPInitializeRequest(WMSHTTPRequest *psRequest) {
95 psRequest->nStatus = 0;
96 psRequest->pabyData = nullptr;
97 psRequest->nDataLen = 0;
98 psRequest->nDataAlloc = 0;
99
100 psRequest->m_curl_handle = curl_easy_init();
101 if (psRequest->m_curl_handle == nullptr) {
102 CPLError(CE_Fatal, CPLE_AppDefined, "CPLHTTPInitializeRequest(): Unable to create CURL handle.");
103 // This should return somehow?
104 }
105
106 if (!psRequest->Range.empty())
107 {
108 CPL_IGNORE_RET_VAL(
109 curl_easy_setopt(psRequest->m_curl_handle, CURLOPT_RANGE, psRequest->Range.c_str()));
110 }
111
112 CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle, CURLOPT_WRITEDATA, psRequest));
113 CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle, CURLOPT_WRITEFUNCTION, WriteFunc));
114
115 psRequest->m_curl_error.resize(CURL_ERROR_SIZE + 1);
116 CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle, CURLOPT_ERRORBUFFER, &psRequest->m_curl_error[0]));
117
118 psRequest->m_headers = static_cast<struct curl_slist*>(
119 CPLHTTPSetOptions(psRequest->m_curl_handle, psRequest->URL.c_str(), psRequest->options));
120 const char* pszAccept = CSLFetchNameValue(psRequest->options, "ACCEPT");
121 if( pszAccept )
122 {
123 psRequest->m_headers = curl_slist_append(psRequest->m_headers,
124 CPLSPrintf("Accept: %s", pszAccept));
125 }
126 if( psRequest->m_headers != nullptr )
127 {
128 CPL_IGNORE_RET_VAL(
129 curl_easy_setopt(psRequest->m_curl_handle, CURLOPT_HTTPHEADER,
130 psRequest->m_headers));
131 }
132
133 }
134
~WMSHTTPRequest()135 WMSHTTPRequest::~WMSHTTPRequest() {
136 if (m_curl_handle != nullptr)
137 curl_easy_cleanup(m_curl_handle);
138 if( m_headers != nullptr )
139 curl_slist_free_all(m_headers);
140 if (pabyData != nullptr)
141 CPLFree(pabyData);
142 }
143
144 //
145 // Like CPLHTTPFetch, but multiple requests in parallel
146 // By default it uses 5 connections
147 //
WMSHTTPFetchMulti(WMSHTTPRequest * pasRequest,int nRequestCount)148 CPLErr WMSHTTPFetchMulti(WMSHTTPRequest *pasRequest, int nRequestCount) {
149 CPLErr ret = CE_None;
150 CURLM *curl_multi = nullptr;
151 int max_conn;
152 int i, conn_i;
153
154 CPLAssert(nRequestCount >= 0);
155 if (nRequestCount == 0)
156 return CE_None;
157
158 const char *max_conn_opt = CSLFetchNameValue(const_cast<char **>(pasRequest->options), "MAXCONN");
159 max_conn = (max_conn_opt == nullptr) ? 5 : MAX(1, MIN(atoi(max_conn_opt), 1000));
160
161 // If the first url starts with vsimem, assume all do and defer to CPLHTTPFetch
162 if( STARTS_WITH(pasRequest[0].URL.c_str(), "/vsimem/") &&
163 /* Disabled by default for potential security issues */
164 CPLTestBool(CPLGetConfigOption("CPL_CURL_ENABLE_VSIMEM", "FALSE")) )
165 {
166 for(i = 0; i< nRequestCount;i++)
167 {
168 CPLHTTPResult* psResult = CPLHTTPFetch(pasRequest[i].URL.c_str(),
169 const_cast<char**>(pasRequest[i].options));
170 pasRequest[i].pabyData = psResult->pabyData;
171 pasRequest[i].nDataLen = psResult->nDataLen;
172 pasRequest[i].Error = psResult->pszErrBuf ? psResult->pszErrBuf : "";
173 // Conventions are different between this module and cpl_http...
174 if( psResult->pszErrBuf != nullptr &&
175 strcmp(psResult->pszErrBuf, "HTTP error code : 404") == 0 )
176 pasRequest[i].nStatus = 404;
177 else
178 pasRequest[i].nStatus = 200;
179 pasRequest[i].ContentType = psResult->pszContentType ? psResult->pszContentType : "";
180 // took ownership of content, we're done with the rest
181 psResult->pabyData = nullptr;
182 psResult->nDataLen = 0;
183 CPLHTTPDestroyResult(psResult);
184 }
185 return CE_None;
186 }
187
188 curl_multi = curl_multi_init();
189 if (curl_multi == nullptr) {
190 CPLError(CE_Fatal, CPLE_AppDefined, "CPLHTTPFetchMulti(): Unable to create CURL multi-handle.");
191 }
192
193 // add at most max_conn requests
194 int torun = std::min(nRequestCount, max_conn);
195 for (conn_i = 0; conn_i < torun; ++conn_i) {
196 WMSHTTPRequest *const psRequest = &pasRequest[conn_i];
197 CPLDebug("HTTP", "Requesting [%d/%d] %s", conn_i + 1, nRequestCount,
198 pasRequest[conn_i].URL.c_str());
199 curl_multi_add_handle(curl_multi, psRequest->m_curl_handle);
200 }
201
202 void* old_handler = CPLHTTPIgnoreSigPipe();
203 int still_running;
204 do {
205 CURLMcode mc;
206 do {
207 mc = curl_multi_perform(curl_multi, &still_running);
208 } while (CURLM_CALL_MULTI_PERFORM == mc);
209
210 // Pick up messages, clean up the completed ones, add more
211 int msgs_in_queue = 0;
212 do {
213 CURLMsg *m = curl_multi_info_read(curl_multi, &msgs_in_queue);
214 if (m && (m->msg == CURLMSG_DONE)) {
215 ProcessCurlErrors(m, pasRequest, nRequestCount);
216
217 curl_multi_remove_handle(curl_multi, m->easy_handle);
218 if (conn_i < nRequestCount) {
219 auto psRequest = &pasRequest[conn_i];
220 CPLDebug("HTTP", "Requesting [%d/%d] %s", conn_i + 1,
221 nRequestCount, pasRequest[conn_i].URL.c_str());
222 curl_multi_add_handle(curl_multi, psRequest->m_curl_handle);
223 ++conn_i;
224 still_running = 1; // Still have request pending
225 }
226 }
227 } while (msgs_in_queue);
228
229 if (CURLM_OK == mc) {
230 int numfds;
231 curl_multi_wait(curl_multi, nullptr, 0, 100, &numfds);
232 }
233 } while (still_running || conn_i != nRequestCount);
234
235 // process any message still in queue
236 CURLMsg* msg;
237 int msgs_in_queue;
238 do {
239 msg = curl_multi_info_read(curl_multi, &msgs_in_queue);
240 if (msg != nullptr) {
241 if (msg->msg == CURLMSG_DONE) {
242 ProcessCurlErrors(msg, pasRequest, nRequestCount);
243 }
244 }
245 } while (msg != nullptr);
246
247 CPLHTTPRestoreSigPipeHandler(old_handler);
248
249 if (conn_i != nRequestCount) { // something gone really really wrong
250 // oddly built libcurl or perhaps absence of network interface
251 CPLError(CE_Failure, CPLE_AppDefined,
252 "WMSHTTPFetchMulti(): conn_i != nRequestCount, this should never happen ...");
253 nRequestCount = conn_i;
254 ret = CE_Failure;
255 }
256
257 for (i = 0; i < nRequestCount; ++i) {
258 WMSHTTPRequest *const psRequest = &pasRequest[i];
259
260 long response_code;
261 curl_easy_getinfo(psRequest->m_curl_handle, CURLINFO_RESPONSE_CODE, &response_code);
262 // for local files, don't update the status code if one is already set
263 if(!(psRequest->nStatus != 0 && STARTS_WITH(psRequest->URL.c_str(), "file://")))
264 psRequest->nStatus = static_cast<int>(response_code);
265
266 char *content_type = nullptr;
267 curl_easy_getinfo(psRequest->m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type);
268 psRequest->ContentType = content_type ? content_type : "";
269
270 if (psRequest->Error.empty())
271 psRequest->Error = &psRequest->m_curl_error[0];
272
273 /* In the case of a file:// URL, curl will return a status == 0, so if there's no */
274 /* error returned, patch the status code to be 200, as it would be for http:// */
275 if (psRequest->nStatus == 0 && psRequest->Error.empty() && STARTS_WITH(psRequest->URL.c_str(), "file://"))
276 psRequest->nStatus = 200;
277
278 // If there is an error with no error message, use the content if it is text
279 if (psRequest->Error.empty()
280 && psRequest->nStatus != 0
281 && psRequest->nStatus != 200
282 && strstr(psRequest->ContentType, "text")
283 && psRequest->pabyData != nullptr )
284 psRequest->Error = reinterpret_cast<const char *>(psRequest->pabyData);
285
286 CPLDebug("HTTP", "Request [%d] %s : status = %d, type = %s, error = %s",
287 i, psRequest->URL.c_str(), psRequest->nStatus,
288 !psRequest->ContentType.empty() ? psRequest->ContentType.c_str() : "(null)",
289 !psRequest->Error.empty() ? psRequest->Error.c_str() : "(null)");
290
291 curl_multi_remove_handle(curl_multi, pasRequest->m_curl_handle);
292 }
293
294 curl_multi_cleanup(curl_multi);
295
296 return ret;
297 }
298