• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MapServer / MapServer / 24101978785

07 Apr 2026 08:07PM UTC coverage: 42.423% (+0.09%) from 42.336%
24101978785

push

github

web-flow
Add support for non-EPSG projections for WCS and WFS protocols (#7457)

* Support AUTH:CODE projections

* Remove duplicate param

* Allow additional authority codes in the wfs_srs list

* Handle custom init files

* Move parameter and make static

* Add back legacy support for urn:EPSG:geographicCRS:CODE

* Tests updated due to new projection handling

* Test updates

* Add new line

* Handle custom projection files and new style proj codes

* EOL fix

* Handle projections in the form urn:ogc:def:crs:ESRI::53009

* Add WCS tests

* Update authorities based on proj.db

* Add projection string tests

* EOL fixes

* Add test data

* Copy custom projection file

* Remove extent

* Add WFS tests

* Update encodings

* Add custom projection tests for WCS

* Remove DATA

* Test fixes

* Switch result to ASCII

* Use backslashes

* Add test for IGNF:ATIGBONNE.BOURD and update IAU_2015

* Appveyor fix

* EOL fix

* Avoid adding + to AUTH:CODE projection strings

* Always assume PROJ_VERSION_MAJOR >= 6

* Use std::string and avoid strlcpy

102 of 120 new or added lines in 5 files covered. (85.0%)

15 existing lines in 5 files now uncovered.

64591 of 152256 relevant lines covered (42.42%)

27322.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.2
/src/maphttp.c
1
/**********************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  Utility functions to access files via HTTP (requires libcurl)
6
 * Author:   Daniel Morissette, DM Solutions Group (morissette@dmsolutions.ca)
7
 *
8
 **********************************************************************
9
 * Copyright (c) 2001-2003, Daniel Morissette, DM Solutions Group Inc
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies of this Software or works derived from this Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 ****************************************************************************/
28

29
#define NEED_IGNORE_RET_VAL
30

31
/* For now this code is enabled only when WMS/WFS client is enabled.
32
 * This should be changed to a test on the presence of libcurl which
33
 * is really what the real dependency is.
34
 */
35
#include "mapserver-config.h"
36
#if defined(USE_CURL)
37

38
#include "mapserver.h"
39
#include "maphttp.h"
40
#include "maperror.h"
41
#include "mapthread.h"
42
#include "mapows.h"
43

44
#include "cpl_conv.h"
45

46
#include <time.h>
47
#ifndef _WIN32
48
#include <sys/time.h>
49
#include <unistd.h>
50

51
#endif
52

53
/*
54
 * Note: This code uses libcurl to access remote files via the HTTP protocol.
55
 * Requires libcurl v 7.10 or more recent.
56
 * See http://curl.haxx.se/libcurl/c/ for the lib source code and docs.
57
 */
58
#include <curl/curl.h>
59

60
#ifndef CURL_AT_LEAST_VERSION
61
#define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | z)
62
#define CURL_AT_LEAST_VERSION(x, y, z)                                         \
63
  (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
64
#endif // #ifndef CURL_AT_LEAST_VERSION
65

66
#define unchecked_curl_easy_setopt(handle, opt, param)                         \
67
  IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
68

69
/**********************************************************************
70
 *                          msHTTPInit()
71
 *
72
 * This function is called to init libcurl before the first HTTP request
73
 * in this process is executed.
74
 * On further calls (when gbCurlInitialized = MS_TRUE) it simply doest nothing.
75
 *
76
 * Returns MS_SUCCESS/MS_FAILURE.
77
 *
78
 * msHTTPCleanup() will have to be called in msCleanup() when this process
79
 * exits.
80
 **********************************************************************/
81
static int gbCurlInitialized = MS_FALSE;
82

83
int msHTTPInit() {
49✔
84
  /* curl_global_init() should only be called once (no matter how
85
   * many threads or libcurl sessions that'll be used) by every
86
   * application that uses libcurl.
87
   */
88

89
  msAcquireLock(TLOCK_OWS);
49✔
90
  if (!gbCurlInitialized && curl_global_init(CURL_GLOBAL_ALL) != 0) {
49✔
91
    msReleaseLock(TLOCK_OWS);
×
92
    msSetError(MS_HTTPERR, "Libcurl initialization failed.", "msHTTPInit()");
×
93
    return MS_FAILURE;
×
94
  }
95

96
  gbCurlInitialized = MS_TRUE;
49✔
97

98
  msReleaseLock(TLOCK_OWS);
49✔
99
  return MS_SUCCESS;
49✔
100
}
101

102
/**********************************************************************
103
 *                          msHTTPCleanup()
104
 *
105
 **********************************************************************/
106
void msHTTPCleanup() {
2,804✔
107
  msAcquireLock(TLOCK_OWS);
2,804✔
108
  if (gbCurlInitialized)
2,804✔
109
    curl_global_cleanup();
49✔
110

111
  gbCurlInitialized = MS_FALSE;
2,804✔
112
  msReleaseLock(TLOCK_OWS);
2,804✔
113
}
2,804✔
114

115
/**********************************************************************
116
 *                          msHTTPInitRequestObj()
117
 *
118
 * Should be called on a new array of httpRequestObj to initialize them
119
 * for use with msHTTPExecuteRequest(), etc.
120
 *
121
 **********************************************************************/
122
void msHTTPInitRequestObj(httpRequestObj *pasReqInfo, int numRequests) {
61✔
123
  int i;
124

125
  for (i = 0; i < numRequests; i++) {
159✔
126
    pasReqInfo[i].pszGetUrl = NULL;
98✔
127
    pasReqInfo[i].pszPostRequest = NULL;
98✔
128
    pasReqInfo[i].pszPostContentType = NULL;
98✔
129
    pasReqInfo[i].pszOutputFile = NULL;
98✔
130
    pasReqInfo[i].pszEPSG = NULL;
98✔
131
    pasReqInfo[i].nLayerId = 0;
98✔
132
    pasReqInfo[i].nTimeout = 0;
98✔
133
    pasReqInfo[i].nMaxBytes = 0;
98✔
134
    pasReqInfo[i].nStatus = 0;
98✔
135
    pasReqInfo[i].pszContentType = NULL;
98✔
136
    pasReqInfo[i].pszErrBuf = NULL;
98✔
137
    pasReqInfo[i].pszUserAgent = NULL;
98✔
138
    pasReqInfo[i].pszHTTPCookieData = NULL;
98✔
139
    pasReqInfo[i].pszProxyAddress = NULL;
98✔
140
    pasReqInfo[i].pszProxyUsername = NULL;
98✔
141
    pasReqInfo[i].pszProxyPassword = NULL;
98✔
142
    pasReqInfo[i].pszHttpUsername = NULL;
98✔
143
    pasReqInfo[i].pszHttpPassword = NULL;
98✔
144

145
    pasReqInfo[i].debug = MS_FALSE;
98✔
146

147
    pasReqInfo[i].curl_handle = NULL;
98✔
148
    pasReqInfo[i].fp = NULL;
98✔
149
    pasReqInfo[i].result_data = NULL;
98✔
150
    pasReqInfo[i].result_size = 0;
98✔
151
    pasReqInfo[i].result_buf_size = 0;
98✔
152
  }
153
}
61✔
154

155
/**********************************************************************
156
 *                          msHTTPFreeRequestObj()
157
 *
158
 **********************************************************************/
159
void msHTTPFreeRequestObj(httpRequestObj *pasReqInfo, int numRequests) {
61✔
160
  int i;
161
  for (i = 0; i < numRequests; i++) {
139✔
162
    if (pasReqInfo[i].pszGetUrl)
78✔
163
      free(pasReqInfo[i].pszGetUrl);
61✔
164
    pasReqInfo[i].pszGetUrl = NULL;
78✔
165

166
    if (pasReqInfo[i].pszPostRequest)
78✔
167
      free(pasReqInfo[i].pszPostRequest);
3✔
168
    pasReqInfo[i].pszPostRequest = NULL;
78✔
169

170
    if (pasReqInfo[i].pszPostContentType)
78✔
171
      free(pasReqInfo[i].pszPostContentType);
3✔
172
    pasReqInfo[i].pszPostContentType = NULL;
78✔
173

174
    if (pasReqInfo[i].pszOutputFile)
78✔
175
      free(pasReqInfo[i].pszOutputFile);
20✔
176
    pasReqInfo[i].pszOutputFile = NULL;
78✔
177

178
    if (pasReqInfo[i].pszEPSG)
78✔
179
      free(pasReqInfo[i].pszEPSG);
41✔
180
    pasReqInfo[i].pszEPSG = NULL;
78✔
181

182
    if (pasReqInfo[i].pszContentType)
78✔
183
      free(pasReqInfo[i].pszContentType);
58✔
184
    pasReqInfo[i].pszContentType = NULL;
78✔
185

186
    if (pasReqInfo[i].pszErrBuf)
78✔
187
      free(pasReqInfo[i].pszErrBuf);
55✔
188
    pasReqInfo[i].pszErrBuf = NULL;
78✔
189

190
    if (pasReqInfo[i].pszUserAgent)
78✔
191
      free(pasReqInfo[i].pszUserAgent);
55✔
192
    pasReqInfo[i].pszUserAgent = NULL;
78✔
193

194
    if (pasReqInfo[i].pszHTTPCookieData)
78✔
195
      free(pasReqInfo[i].pszHTTPCookieData);
×
196
    pasReqInfo[i].pszHTTPCookieData = NULL;
78✔
197

198
    pasReqInfo[i].curl_handle = NULL;
78✔
199

200
    free(pasReqInfo[i].result_data);
78✔
201
    pasReqInfo[i].result_data = NULL;
78✔
202
    pasReqInfo[i].result_size = 0;
78✔
203
    pasReqInfo[i].result_buf_size = 0;
78✔
204
  }
205
}
61✔
206

207
/**********************************************************************
208
 *                          msHTTPWriteFct()
209
 *
210
 * CURL_OPTWRITEFUNCTION, called to write blocks of data to the file we
211
 * are downloading.  Should return the number of bytes that were taken
212
 * care of.  If that  amount  differs  from  the  amount passed to it
213
 * it'll signal an error to the library and it will abort the transfer
214
 * and produce a CURLE_WRITE_ERROR.
215
 *
216
 **********************************************************************/
217
static size_t msHTTPWriteFct(void *buffer, size_t size, size_t nmemb,
294✔
218
                             void *reqInfo) {
219
  httpRequestObj *psReq;
220

221
  psReq = (httpRequestObj *)reqInfo;
222

223
  if (psReq->debug) {
294✔
224
    msDebug("msHTTPWriteFct(id=%d, %d bytes)\n", psReq->nLayerId,
22✔
225
            (int)(size * nmemb));
226
  }
227

228
  if (psReq->nMaxBytes > 0 &&
294✔
229
      (psReq->result_size + size * nmemb) > (size_t)psReq->nMaxBytes) {
152✔
230
    msSetError(MS_HTTPERR,
1✔
231
               "Requested transfer larger than configured maximum %d.",
232
               "msHTTPWriteFct()", psReq->nMaxBytes);
233
    return -1;
1✔
234
  }
235
  /* Case where we are writing to a disk file. */
236
  if (psReq->fp != NULL) {
293✔
237
    psReq->result_size += size * nmemb;
154✔
238
    return fwrite(buffer, size, nmemb, psReq->fp);
154✔
239
  }
240

241
  /* Case where we build up the result in memory */
242
  else {
243
    if (psReq->result_data == NULL) {
139✔
244
      psReq->result_buf_size = size * nmemb + 10000;
37✔
245
      psReq->result_data = (char *)msSmallMalloc(psReq->result_buf_size);
37✔
246
    } else if (psReq->result_size + nmemb * size >
102✔
247
               (size_t)psReq->result_buf_size) {
102✔
248
      psReq->result_buf_size = psReq->result_size + nmemb * size + 10000;
26✔
249
      psReq->result_data =
26✔
250
          (char *)msSmallRealloc(psReq->result_data, psReq->result_buf_size);
26✔
251
    }
252

253
    if (psReq->result_data == NULL) {
139✔
254
      msSetError(MS_HTTPERR, "Unable to grow HTTP result buffer to size %d.",
×
255
                 "msHTTPWriteFct()", psReq->result_buf_size);
256
      psReq->result_buf_size = 0;
×
257
      psReq->result_size = 0;
×
258
      return -1;
×
259
    }
260

261
    memcpy(psReq->result_data + psReq->result_size, buffer, size * nmemb);
139✔
262
    psReq->result_size += size * nmemb;
139✔
263

264
    return size * nmemb;
139✔
265
  }
266
}
267

268
/**********************************************************************
269
 *                          msGetCURLAuthType()
270
 *
271
 * Returns the equivalent CURL CURLAUTH_ constant given a
272
 * MS_HTTP_AUTH_TYPE, or CURLAUTH_BASIC if no match is found.
273
 **********************************************************************/
274
long msGetCURLAuthType(enum MS_HTTP_AUTH_TYPE authType) {
×
275
  switch (authType) {
276
  case MS_BASIC:
277
    return CURLAUTH_BASIC;
278
  case MS_DIGEST:
279
    return CURLAUTH_DIGEST;
280
  case MS_NTLM:
281
    return CURLAUTH_NTLM;
282
  case MS_ANY:
283
    return CURLAUTH_ANY;
284
  case MS_ANYSAFE:
285
    return CURLAUTH_ANYSAFE;
286
  default:
287
    return CURLAUTH_BASIC;
288
  }
289
}
290

291
/**********************************************************************
292
 *                          msHTTPAuthProxySetup()
293
 *
294
 * Common code used by msPrepareWFSLayerRequest() and
295
 * msPrepareWMSLayerRequest() to handle proxy / http auth for requests
296
 *
297
 * Return value:
298
 * MS_SUCCESS if all requests completed successfully.
299
 * MS_FAILURE if a fatal error happened
300
 **********************************************************************/
301
int msHTTPAuthProxySetup(hashTableObj *mapmd, hashTableObj *lyrmd,
44✔
302
                         httpRequestObj *pasReqInfo, int numRequests,
303
                         mapObj *map, const char *namespaces) {
304

305
  const char *pszTmp;
306
  char *pszProxyHost = NULL;
307
  long nProxyPort = 0;
308
  char *pszProxyUsername = NULL, *pszProxyPassword = NULL;
309
  char *pszHttpAuthUsername = NULL, *pszHttpAuthPassword = NULL;
310
  enum MS_HTTP_AUTH_TYPE eHttpAuthType = MS_BASIC;
311
  enum MS_HTTP_AUTH_TYPE eProxyAuthType = MS_BASIC;
312
  enum MS_HTTP_PROXY_TYPE eProxyType = MS_HTTP;
313

314
  /* ------------------------------------------------------------------
315
   * Check for authentication and proxying metadata. If the metadata is not
316
   * found in the layer metadata, check the map-level metadata.
317
   * ------------------------------------------------------------------ */
318
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces, "proxy_host")) !=
44✔
319
      NULL) {
320
    pszProxyHost = msStrdup(pszTmp);
×
321
  }
322

323
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces, "proxy_port")) !=
44✔
324
      NULL) {
325
    nProxyPort = atol(pszTmp);
326
  }
327

328
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces, "proxy_type")) !=
44✔
329
      NULL) {
330

331
    if (strcasecmp(pszTmp, "HTTP") == 0)
×
332
      eProxyType = MS_HTTP;
333
    else if (strcasecmp(pszTmp, "SOCKS5") == 0)
×
334
      eProxyType = MS_SOCKS5;
335
    else {
336
      msSetError(MS_WMSERR, "Invalid proxy_type metadata '%s' specified",
×
337
                 "msHTTPAuthProxySetup()", pszTmp);
338
      return MS_FAILURE;
×
339
    }
340
  }
341

342
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
44✔
343
                                     "proxy_auth_type")) != NULL) {
344
    if (strcasecmp(pszTmp, "BASIC") == 0)
×
345
      eProxyAuthType = MS_BASIC;
346
    else if (strcasecmp(pszTmp, "DIGEST") == 0)
×
347
      eProxyAuthType = MS_DIGEST;
348
    else if (strcasecmp(pszTmp, "NTLM") == 0)
×
349
      eProxyAuthType = MS_NTLM;
350
    else if (strcasecmp(pszTmp, "ANY") == 0)
×
351
      eProxyAuthType = MS_ANY;
352
    else if (strcasecmp(pszTmp, "ANYSAFE") == 0)
×
353
      eProxyAuthType = MS_ANYSAFE;
354
    else {
355
      msSetError(MS_WMSERR, "Invalid proxy_auth_type metadata '%s' specified",
×
356
                 "msHTTPAuthProxySetup()", pszTmp);
357
      return MS_FAILURE;
×
358
    }
359
  }
360

361
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
44✔
362
                                     "proxy_username")) != NULL) {
363
    pszProxyUsername = msStrdup(pszTmp);
×
364
  }
365

366
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
44✔
367
                                     "proxy_password")) != NULL) {
368
    pszProxyPassword = msDecryptStringTokens(map, pszTmp);
×
369
    if (pszProxyPassword == NULL) {
×
370
      msFree(pszProxyHost);
×
371
      msFree(pszProxyUsername);
×
372
      return (MS_FAILURE); /* An error should already have been produced */
×
373
    }
374
  }
375

376
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces, "auth_type")) !=
44✔
377
      NULL) {
378
    if (strcasecmp(pszTmp, "BASIC") == 0)
×
379
      eHttpAuthType = MS_BASIC;
380
    else if (strcasecmp(pszTmp, "DIGEST") == 0)
×
381
      eHttpAuthType = MS_DIGEST;
382
    else if (strcasecmp(pszTmp, "NTLM") == 0)
×
383
      eHttpAuthType = MS_NTLM;
384
    else if (strcasecmp(pszTmp, "ANY") == 0)
×
385
      eHttpAuthType = MS_ANY;
386
    else if (strcasecmp(pszTmp, "ANYSAFE") == 0)
×
387
      eHttpAuthType = MS_ANYSAFE;
388
    else {
389
      msSetError(MS_WMSERR, "Invalid auth_type metadata '%s' specified",
×
390
                 "msHTTPAuthProxySetup()", pszTmp);
391
      return MS_FAILURE;
×
392
    }
393
  }
394

395
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
44✔
396
                                     "auth_username")) != NULL) {
397
    pszHttpAuthUsername = msStrdup(pszTmp);
×
398
  }
399

400
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
44✔
401
                                     "auth_password")) != NULL) {
402
    pszHttpAuthPassword = msDecryptStringTokens(map, pszTmp);
×
403
    if (pszHttpAuthPassword == NULL) {
×
404
      msFree(pszHttpAuthUsername);
×
405
      msFree(pszProxyHost);
×
406
      msFree(pszProxyUsername);
×
407
      msFree(pszProxyPassword);
×
408
      return (MS_FAILURE); /* An error should already have been produced */
×
409
    }
410
  }
411

412
  pasReqInfo[numRequests].pszProxyAddress = pszProxyHost;
44✔
413
  pasReqInfo[numRequests].nProxyPort = nProxyPort;
44✔
414
  pasReqInfo[numRequests].eProxyType = eProxyType;
44✔
415
  pasReqInfo[numRequests].eProxyAuthType = eProxyAuthType;
44✔
416
  pasReqInfo[numRequests].pszProxyUsername = pszProxyUsername;
44✔
417
  pasReqInfo[numRequests].pszProxyPassword = pszProxyPassword;
44✔
418
  pasReqInfo[numRequests].eHttpAuthType = eHttpAuthType;
44✔
419
  pasReqInfo[numRequests].pszHttpUsername = pszHttpAuthUsername;
44✔
420
  pasReqInfo[numRequests].pszHttpPassword = pszHttpAuthPassword;
44✔
421

422
  return MS_SUCCESS;
44✔
423
}
424

425
/**********************************************************************
426
 *                          msHTTPExecuteRequests()
427
 *
428
 * Fetch a map slide via HTTP request and save to specified temp file.
429
 *
430
 * If bCheckLocalCache==MS_TRUE then if the pszOutputfile already exists
431
 * then it is not downloaded again, and status 242 is returned.
432
 *
433
 * Return value:
434
 * MS_SUCCESS if all requests completed successfully.
435
 * MS_FAILURE if a fatal error happened
436
 * MS_DONE if some requests failed with 40x status for instance (not fatal)
437
 **********************************************************************/
438
int msHTTPExecuteRequests(httpRequestObj *pasReqInfo, int numRequests,
61✔
439
                          int bCheckLocalCache) {
440
  int i, nStatus = MS_SUCCESS, nTimeout, still_running = 0, num_msgs = 0;
61✔
441
  CURLM *multi_handle;
442
  CURLMsg *curl_msg;
443
  char debug = MS_FALSE;
444
  const char *pszCurlCABundle = NULL;
445

446
  if (numRequests == 0)
61✔
447
    return MS_SUCCESS; /* Nothing to do */
448

449
  if (!gbCurlInitialized)
61✔
450
    msHTTPInit();
49✔
451

452
  /* Establish the timeout (seconds) for how long we are going to wait
453
   * for a response.
454
   * We use the longest timeout value in the array of requests
455
   */
456
  nTimeout = pasReqInfo[0].nTimeout;
61✔
457
  for (i = 0; i < numRequests; i++) {
122✔
458
    if (pasReqInfo[i].nTimeout > nTimeout)
61✔
459
      nTimeout = pasReqInfo[i].nTimeout;
460

461
    if (pasReqInfo[i].debug)
61✔
462
      debug = MS_TRUE; /* For the download loop */
463
  }
464

465
  if (nTimeout <= 0)
61✔
466
    nTimeout = 30;
467

468
  /* Check if we've got a CURL_CA_BUNDLE env. var.
469
   * If set then the value is the full path to the ca-bundle.crt file
470
   * e.g. CURL_CA_BUNDLE=/usr/local/share/curl/curl-ca-bundle.crt
471
   */
472
  pszCurlCABundle = CPLGetConfigOption("CURL_CA_BUNDLE", NULL);
61✔
473

474
  if (debug) {
61✔
475
    msDebug("HTTP: Starting to prepare HTTP requests.\n");
11✔
476
    if (pszCurlCABundle)
11✔
477
      msDebug("Using CURL_CA_BUNDLE=%s\n", pszCurlCABundle);
×
478
  }
479

480
  const char *pszHttpVersion = CPLGetConfigOption("CURL_HTTP_VERSION", NULL);
61✔
481

482
  /* Alloc a curl-multi handle, and add a curl-easy handle to it for each
483
   * file to download.
484
   */
485
  multi_handle = curl_multi_init();
61✔
486
  if (multi_handle == NULL) {
61✔
487
    msSetError(MS_HTTPERR, "curl_multi_init() failed.",
×
488
               "msHTTPExecuteRequests()");
489
    return (MS_FAILURE);
×
490
  }
491

492
  for (i = 0; i < numRequests; i++) {
122✔
493
    CURL *http_handle;
494
    FILE *fp;
495

496
    if (pasReqInfo[i].pszGetUrl == NULL) {
61✔
497
      msSetError(MS_HTTPERR, "URL or output file parameter missing.",
×
498
                 "msHTTPExecuteRequests()");
499
      return (MS_FAILURE);
×
500
    }
501

502
    if (pasReqInfo[i].debug) {
61✔
503
      msDebug("HTTP request: id=%d, %s\n", pasReqInfo[i].nLayerId,
11✔
504
              pasReqInfo[i].pszGetUrl);
505
    }
506

507
    /* Reset some members */
508
    pasReqInfo[i].nStatus = 0;
61✔
509
    if (pasReqInfo[i].pszContentType)
61✔
510
      free(pasReqInfo[i].pszContentType);
×
511
    pasReqInfo[i].pszContentType = NULL;
61✔
512

513
    /* Check local cache if requested */
514
    if (bCheckLocalCache && pasReqInfo[i].pszOutputFile != NULL) {
61✔
515
      fp = fopen(pasReqInfo[i].pszOutputFile, "r");
13✔
516
      if (fp) {
13✔
517
        /* File already there, don't download again. */
518
        if (pasReqInfo[i].debug)
6✔
519
          msDebug("HTTP request: id=%d, found in cache, skipping.\n",
×
520
                  pasReqInfo[i].nLayerId);
521
        fclose(fp);
6✔
522
        pasReqInfo[i].nStatus = 242;
6✔
523
        pasReqInfo[i].pszContentType = msStrdup("unknown/cached");
6✔
524
        continue;
6✔
525
      }
526
    }
527

528
    /* Alloc curl handle */
529
    http_handle = curl_easy_init();
55✔
530
    if (http_handle == NULL) {
55✔
531
      msSetError(MS_HTTPERR, "curl_easy_init() failed.",
×
532
                 "msHTTPExecuteRequests()");
533
      return (MS_FAILURE);
×
534
    }
535

536
    pasReqInfo[i].curl_handle = http_handle;
55✔
537

538
    /* set URL, note that curl keeps only a ref to our string buffer */
539
    unchecked_curl_easy_setopt(http_handle, CURLOPT_URL,
55✔
540
                               pasReqInfo[i].pszGetUrl);
541

542
#if CURL_AT_LEAST_VERSION(7, 85, 0)
543
    unchecked_curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS_STR,
55✔
544
                               "http,https");
545
#else
546
    unchecked_curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS,
547
                               CURLPROTO_HTTP | CURLPROTO_HTTPS);
548
#endif
549

550
    if (pszHttpVersion && strcmp(pszHttpVersion, "1.0") == 0)
55✔
551
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
×
552
                                 CURL_HTTP_VERSION_1_0);
553
    else if (pszHttpVersion && strcmp(pszHttpVersion, "1.1") == 0)
44✔
554
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
44✔
555
                                 CURL_HTTP_VERSION_1_1);
556

557
    /* Set User-Agent (auto-generate if not set by caller */
558
    if (pasReqInfo[i].pszUserAgent == NULL) {
55✔
559
      curl_version_info_data *psCurlVInfo;
560

561
      psCurlVInfo = curl_version_info(CURLVERSION_NOW);
55✔
562

563
      pasReqInfo[i].pszUserAgent = (char *)msSmallMalloc(100 * sizeof(char));
55✔
564

565
      if (pasReqInfo[i].pszUserAgent) {
55✔
566
        sprintf(pasReqInfo[i].pszUserAgent, "MapServer/%s libcurl/%d.%d.%d",
55✔
567
                MS_VERSION, psCurlVInfo->version_num / 0x10000 & 0xff,
55✔
568
                psCurlVInfo->version_num / 0x100 & 0xff,
55✔
569
                psCurlVInfo->version_num & 0xff);
55✔
570
      }
571
    }
572
    if (pasReqInfo[i].pszUserAgent) {
55✔
573
      unchecked_curl_easy_setopt(http_handle, CURLOPT_USERAGENT,
55✔
574
                                 pasReqInfo[i].pszUserAgent);
575
    }
576

577
    /* Enable following redirections.  Requires libcurl 7.10.1 at least */
578
    unchecked_curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1);
55✔
579
    unchecked_curl_easy_setopt(http_handle, CURLOPT_MAXREDIRS, 10);
55✔
580

581
    /* Set timeout.*/
582
    unchecked_curl_easy_setopt(http_handle, CURLOPT_TIMEOUT, nTimeout);
55✔
583

584
    /* Pass CURL_CA_BUNDLE if set */
585
    if (pszCurlCABundle)
55✔
586
      unchecked_curl_easy_setopt(http_handle, CURLOPT_CAINFO, pszCurlCABundle);
×
587

588
    /* Set proxying settings */
589
    if (pasReqInfo[i].pszProxyAddress != NULL &&
55✔
590
        strlen(pasReqInfo[i].pszProxyAddress) > 0) {
×
591
      long nProxyType = CURLPROXY_HTTP;
592

593
      unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXY,
×
594
                                 pasReqInfo[i].pszProxyAddress);
595

596
      if (pasReqInfo[i].nProxyPort > 0 && pasReqInfo[i].nProxyPort < 65535) {
×
597
        unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYPORT,
×
598
                                   pasReqInfo[i].nProxyPort);
599
      }
600

601
      switch (pasReqInfo[i].eProxyType) {
×
602
      case MS_HTTP:
603
        nProxyType = CURLPROXY_HTTP;
604
        break;
605
      case MS_SOCKS5:
×
606
        nProxyType = CURLPROXY_SOCKS5;
607
        break;
×
608
      }
609
      unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYTYPE, nProxyType);
×
610

611
      /* If there is proxy authentication information, set it */
612
      if (pasReqInfo[i].pszProxyUsername != NULL &&
×
613
          pasReqInfo[i].pszProxyPassword != NULL &&
×
614
          strlen(pasReqInfo[i].pszProxyUsername) > 0 &&
×
615
          strlen(pasReqInfo[i].pszProxyPassword) > 0) {
×
616
        char szUsernamePasswd[128];
617
#if LIBCURL_VERSION_NUM >= 0x070a07
618
        long nProxyAuthType = CURLAUTH_BASIC;
619
        /* CURLOPT_PROXYAUTH available only in Curl 7.10.7 and up */
620
        nProxyAuthType = msGetCURLAuthType(pasReqInfo[i].eProxyAuthType);
×
621
        unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH,
×
622
                                   nProxyAuthType);
623
#else
624
        /* We log an error but don't abort processing */
625
        msSetError(MS_HTTPERR,
626
                   "CURLOPT_PROXYAUTH not supported. Requires Curl 7.10.7 and "
627
                   "up. *_proxy_auth_type setting ignored.",
628
                   "msHTTPExecuteRequests()");
629
#endif /* LIBCURL_VERSION_NUM */
630

631
        snprintf(szUsernamePasswd, 127, "%s:%s", pasReqInfo[i].pszProxyUsername,
×
632
                 pasReqInfo[i].pszProxyPassword);
633
        unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYUSERPWD,
×
634
                                   szUsernamePasswd);
635
      }
636
    }
637

638
    /* Set HTTP Authentication settings */
639
    if (pasReqInfo[i].pszHttpUsername != NULL &&
55✔
640
        pasReqInfo[i].pszHttpPassword != NULL &&
×
641
        strlen(pasReqInfo[i].pszHttpUsername) > 0 &&
×
642
        strlen(pasReqInfo[i].pszHttpPassword) > 0) {
×
643
      char szUsernamePasswd[128];
644
      long nHttpAuthType = CURLAUTH_BASIC;
645

646
      snprintf(szUsernamePasswd, 127, "%s:%s", pasReqInfo[i].pszHttpUsername,
647
               pasReqInfo[i].pszHttpPassword);
648
      unchecked_curl_easy_setopt(http_handle, CURLOPT_USERPWD,
×
649
                                 szUsernamePasswd);
650

651
      nHttpAuthType = msGetCURLAuthType(pasReqInfo[i].eHttpAuthType);
×
652
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH, nHttpAuthType);
×
653
    }
654

655
    /* NOSIGNAL should be set to true for timeout to work in multithread
656
     * environments on Unix, requires libcurl 7.10 or more recent.
657
     * (this force avoiding the use of sgnal handlers)
658
     */
659
#ifdef CURLOPT_NOSIGNAL
660
    unchecked_curl_easy_setopt(http_handle, CURLOPT_NOSIGNAL, 1);
661
#endif
662

663
    /* If we are writing file to disk, open the file now. */
664
    if (pasReqInfo[i].pszOutputFile != NULL) {
55✔
665
      if ((fp = fopen(pasReqInfo[i].pszOutputFile, "wb")) == NULL) {
14✔
666
        msSetError(MS_HTTPERR, "Can't open output file %s.",
×
667
                   "msHTTPExecuteRequests()", pasReqInfo[i].pszOutputFile);
668
        return (MS_FAILURE);
×
669
      }
670

671
      pasReqInfo[i].fp = fp;
14✔
672
    }
673

674
    /* coverity[bad_sizeof] */
675
    unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEDATA,
55✔
676
                               &(pasReqInfo[i]));
677
    unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION,
55✔
678
                               msHTTPWriteFct);
679

680
    /* Provide a buffer where libcurl can write human readable error msgs
681
     */
682
    if (pasReqInfo[i].pszErrBuf == NULL)
55✔
683
      pasReqInfo[i].pszErrBuf =
55✔
684
          (char *)msSmallMalloc((CURL_ERROR_SIZE + 1) * sizeof(char));
55✔
685
    pasReqInfo[i].pszErrBuf[0] = '\0';
55✔
686

687
    unchecked_curl_easy_setopt(http_handle, CURLOPT_ERRORBUFFER,
55✔
688
                               pasReqInfo[i].pszErrBuf);
689

690
    pasReqInfo[i].curl_headers = NULL;
55✔
691

692
    if (pasReqInfo[i].pszPostRequest != NULL) {
55✔
693
      char szBuf[100];
694

695
      snprintf(szBuf, 100, "Content-Type: %s",
3✔
696
               pasReqInfo[i].pszPostContentType);
697
      pasReqInfo[i].curl_headers = curl_slist_append(NULL, szBuf);
3✔
698

699
      unchecked_curl_easy_setopt(http_handle, CURLOPT_POST, 1);
3✔
700
      unchecked_curl_easy_setopt(http_handle, CURLOPT_POSTFIELDS,
3✔
701
                                 pasReqInfo[i].pszPostRequest);
702
      if (debug) {
3✔
703
        msDebug("HTTP: POST = %s", pasReqInfo[i].pszPostRequest);
×
704
      }
705
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPHEADER,
3✔
706
                                 pasReqInfo[i].curl_headers);
707
    }
708

709
    /* Added by RFC-42 HTTP Cookie Forwarding */
710
    if (pasReqInfo[i].pszHTTPCookieData != NULL) {
55✔
711
      /* Check if there's no end of line in the Cookie string */
712
      /* This could break the HTTP Header */
713
      for (size_t nPos = 0; nPos < strlen(pasReqInfo[i].pszHTTPCookieData);
×
714
           nPos++) {
×
715
        if (pasReqInfo[i].pszHTTPCookieData[nPos] == '\n') {
×
716
          msSetError(MS_HTTPERR,
×
717
                     "Can't use cookie containing a newline character.",
718
                     "msHTTPExecuteRequests()");
719
          return (MS_FAILURE);
×
720
        }
721
      }
722

723
      /* Set the Curl option to send Cookie */
724
      unchecked_curl_easy_setopt(http_handle, CURLOPT_COOKIE,
×
725
                                 pasReqInfo[i].pszHTTPCookieData);
726
    }
727

728
    /* Add to multi handle */
729
    curl_multi_add_handle(multi_handle, http_handle);
55✔
730
  }
731

732
  if (debug) {
61✔
733
    msDebug("HTTP: Before download loop\n");
11✔
734
  }
735

736
  /* DOWNLOAD LOOP ... inspired from multi-double.c example */
737

738
  /* we start some action by calling perform right away */
739
  while (CURLM_CALL_MULTI_PERFORM ==
61✔
740
         curl_multi_perform(multi_handle, &still_running))
61✔
741
    ;
742

743
  while (still_running) {
494✔
744
    struct timeval timeout;
745
    int rc; /* select() return code */
746

747
    fd_set fdread;
748
    fd_set fdwrite;
749
    fd_set fdexcep;
750
    int maxfd;
751

752
    FD_ZERO(&fdread);
7,361✔
753
    FD_ZERO(&fdwrite);
7,361✔
754
    FD_ZERO(&fdexcep);
7,361✔
755

756
    /* set a suitable timeout to play around with */
757
    timeout.tv_sec = 0;
433✔
758
    timeout.tv_usec = 100000;
433✔
759

760
    /* get file descriptors from the transfers */
761
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
433✔
762

763
    rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
433✔
764

765
    switch (rc) {
433✔
766
    case -1:
767
      /* select error */
768

769
      /* ==================================================================== */
770
      /*      On Windows the select function (just above) returns -1 when     */
771
      /*      it is called the second time and all the calls after            */
772
      /*      that. This causes an infinite loop.                             */
773
      /*      I do not really know why.                                       */
774
      /*      To solve the problem the break from case -1 has been removed.   */
775
      /* ==================================================================== */
776
#ifndef _WIN32
777
      break;
778
#endif
779
    case 0:
433✔
780
    default:
781
      /* timeout or readable/writable sockets */
782
      curl_multi_perform(multi_handle, &still_running);
433✔
783
      break;
433✔
784
    }
785
  }
786

787
  if (debug)
61✔
788
    msDebug("HTTP: After download loop\n");
11✔
789

790
  /* Scan message stack from CURL and report fatal errors*/
791

792
  while ((curl_msg = curl_multi_info_read(multi_handle, &num_msgs)) != NULL) {
116✔
793
    httpRequestObj *psReq = NULL;
794

795
    if (curl_msg->msg == CURLMSG_DONE && curl_msg->data.result != CURLE_OK) {
55✔
796
      /* Something went wrong with this transfer... report error */
797

798
      for (i = 0; i < numRequests; i++) {
3✔
799
        if (pasReqInfo[i].curl_handle == curl_msg->easy_handle) {
3✔
800
          psReq = &(pasReqInfo[i]);
801
          break;
802
        }
803
      }
804

805
      if (psReq != NULL) {
3✔
806
        /* Record error code in nStatus as a negative value */
807
        psReq->nStatus = -curl_msg->data.result;
3✔
808
      }
809
    }
810
  }
811

812
  if (debug) {
61✔
813
    /* Print a msDebug header for timings reported in the loop below */
814
    msDebug("msHTTPExecuteRequests() timing summary per layer (connect_time + "
11✔
815
            "time_to_first_packet + download_time = total_time in seconds)\n");
816
  }
817

818
  /* Check status of all requests, close files, report errors and cleanup
819
   * handles
820
   */
821
  for (i = 0; i < numRequests; i++) {
122✔
822
    httpRequestObj *psReq;
823
    CURL *http_handle;
824
    long lVal = 0;
61✔
825

826
    psReq = &(pasReqInfo[i]);
61✔
827

828
    if (psReq->nStatus == 242)
61✔
829
      continue; /* Nothing to do here, this file was in cache already */
6✔
830

831
    if (psReq->fp)
55✔
832
      fclose(psReq->fp);
14✔
833
    psReq->fp = NULL;
55✔
834

835
    http_handle = (CURL *)(psReq->curl_handle);
55✔
836

837
    if (psReq->nStatus == 0 &&
107✔
838
        curl_easy_getinfo(http_handle, CURLINFO_HTTP_CODE, &lVal) == CURLE_OK) {
52✔
839
      char *pszContentType = NULL;
52✔
840

841
      psReq->nStatus = lVal;
52✔
842

843
      /* Fetch content type of response */
844
      if (curl_easy_getinfo(http_handle, CURLINFO_CONTENT_TYPE,
52✔
845
                            &pszContentType) == CURLE_OK &&
52✔
846
          pszContentType != NULL) {
52✔
847
        psReq->pszContentType = msStrdup(pszContentType);
52✔
848
      }
849
    }
850

851
    if (!MS_HTTP_SUCCESS(psReq->nStatus)) {
55✔
852
      /* Set status to MS_DONE to indicate that transfers were  */
853
      /* completed but may not be successful */
854
      nStatus = MS_DONE;
855

856
      if (psReq->nStatus == -(CURLE_OPERATION_TIMEOUTED)) {
4✔
857
        /* Timeout isn't a fatal error */
UNCOV
858
        if (psReq->debug)
×
UNCOV
859
          msDebug("HTTP: TIMEOUT of %d seconds exceeded for %s\n", nTimeout,
×
860
                  psReq->pszGetUrl);
861

UNCOV
862
        msSetError(MS_HTTPERR, "HTTP: TIMEOUT of %d seconds exceeded for %s\n",
×
863
                   "msHTTPExecuteRequests()", nTimeout, psReq->pszGetUrl);
864

865
        /* Rewrite error message, the curl timeout message isn't
866
         * of much use to our users.
867
         */
UNCOV
868
        sprintf(psReq->pszErrBuf, "TIMEOUT of %d seconds exceeded.", nTimeout);
×
869
      } else if (psReq->nStatus > 0) {
4✔
870
        /* Got an HTTP Error, e.g. 404, etc. */
871

872
        if (psReq->debug)
1✔
UNCOV
873
          msDebug("HTTP: HTTP GET request failed with status %d (%s)"
×
874
                  " for %s\n",
875
                  psReq->nStatus, psReq->pszErrBuf, psReq->pszGetUrl);
876

877
        msSetError(MS_HTTPERR,
1✔
878
                   "HTTP GET request failed with status %d (%s) "
879
                   "for %s",
880
                   "msHTTPExecuteRequests()", psReq->nStatus, psReq->pszErrBuf,
881
                   psReq->pszGetUrl);
882
      } else {
883
        /* Got a curl error */
884

885
        errorObj *error = msGetErrorObj();
3✔
886
        if (psReq->debug)
3✔
887
          msDebug("HTTP: request failed with curl error "
×
888
                  "code %d (%s) for %s",
889
                  -psReq->nStatus, psReq->pszErrBuf, psReq->pszGetUrl);
×
890

891
        if (!error ||
3✔
892
            error->code ==
3✔
893
                MS_NOERR) /* only set error if one hasn't already been set */
894
          msSetError(MS_HTTPERR,
2✔
895
                     "HTTP: request failed with curl error "
896
                     "code %d (%s) for %s",
897
                     "msHTTPExecuteRequests()", -psReq->nStatus,
2✔
898
                     psReq->pszErrBuf, psReq->pszGetUrl);
899
      }
900
    }
901

902
    /* Report download times foreach handle, in debug mode */
903
    if (psReq->debug) {
55✔
904
      double dConnectTime = 0.0, dTotalTime = 0.0, dStartTfrTime = 0.0;
11✔
905

906
      curl_easy_getinfo(http_handle, CURLINFO_CONNECT_TIME, &dConnectTime);
11✔
907
      curl_easy_getinfo(http_handle, CURLINFO_STARTTRANSFER_TIME,
11✔
908
                        &dStartTfrTime);
909
      curl_easy_getinfo(http_handle, CURLINFO_TOTAL_TIME, &dTotalTime);
11✔
910
      /* STARTTRANSFER_TIME includes CONNECT_TIME, but TOTAL_TIME
911
       * doesn't, so we need to add it.
912
       */
913
      dTotalTime += dConnectTime;
11✔
914

915
      msDebug("Layer %d: %.3f + %.3f + %.3f = %.3fs\n", psReq->nLayerId,
11✔
916
              dConnectTime, dStartTfrTime - dConnectTime,
917
              dTotalTime - dStartTfrTime, dTotalTime);
918
    }
919

920
    /* Cleanup this handle */
921
    unchecked_curl_easy_setopt(http_handle, CURLOPT_URL, "");
55✔
922
    curl_multi_remove_handle(multi_handle, http_handle);
55✔
923
    curl_easy_cleanup(http_handle);
55✔
924
    psReq->curl_handle = NULL;
55✔
925
    curl_slist_free_all(psReq->curl_headers); // free the header list
55✔
926
  }
927

928
  /* Cleanup multi handle, each handle had to be cleaned up individually */
929
  curl_multi_cleanup(multi_handle);
61✔
930

931
  return nStatus;
61✔
932
}
933

934
/**********************************************************************
935
 *                          msHTTPGetFile()
936
 *
937
 * Wrapper to call msHTTPExecuteRequests() for a single file.
938
 **********************************************************************/
939
int msHTTPGetFile(const char *pszGetUrl, const char *pszOutputFile,
17✔
940
                  int *pnHTTPStatus, int nTimeout, int bCheckLocalCache,
941
                  int bDebug, int nMaxBytes) {
942
  httpRequestObj *pasReqInfo;
943

944
  /* Alloc httpRequestInfo structs through which status of each request
945
   * will be returned.
946
   * We need to alloc 2 instance of requestobj so that the last
947
   * object in the array can be set to NULL.
948
   */
949
  pasReqInfo = (httpRequestObj *)calloc(2, sizeof(httpRequestObj));
17✔
950
  MS_CHECK_ALLOC(pasReqInfo, 2 * sizeof(httpRequestObj), MS_FAILURE);
17✔
951

952
  msHTTPInitRequestObj(pasReqInfo, 2);
17✔
953

954
  pasReqInfo[0].pszGetUrl = msStrdup(pszGetUrl);
17✔
955
  pasReqInfo[0].pszOutputFile = msStrdup(pszOutputFile);
17✔
956
  pasReqInfo[0].debug = (char)bDebug;
17✔
957
  pasReqInfo[0].nTimeout = nTimeout;
17✔
958
  pasReqInfo[0].nMaxBytes = nMaxBytes;
17✔
959

960
  if (msHTTPExecuteRequests(pasReqInfo, 1, bCheckLocalCache) != MS_SUCCESS) {
17✔
961
    *pnHTTPStatus = pasReqInfo[0].nStatus;
4✔
962
    if (pasReqInfo[0].debug)
4✔
963
      msDebug("HTTP request failed for %s.\n", pszGetUrl);
×
964
    msHTTPFreeRequestObj(pasReqInfo, 2);
4✔
965
    free(pasReqInfo);
4✔
966
    return MS_FAILURE;
4✔
967
  }
968

969
  *pnHTTPStatus = pasReqInfo[0].nStatus;
13✔
970

971
  msHTTPFreeRequestObj(pasReqInfo, 2);
13✔
972
  free(pasReqInfo);
13✔
973

974
  return MS_SUCCESS;
13✔
975
}
976

977
#endif /* defined(USE_WMS_LYR) || defined(USE_WMS_SVR) */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc