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

geographika / mapserver / 13214902618

08 Feb 2025 10:44AM UTC coverage: 40.383% (-0.004%) from 40.387%
13214902618

push

github

geographika
Switch to demo.mapserver.org for test

58466 of 144780 relevant lines covered (40.38%)

25545.22 hits per line

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

74.93
/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() {
36✔
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);
36✔
90
  if (!gbCurlInitialized && curl_global_init(CURL_GLOBAL_ALL) != 0) {
36✔
91
    msReleaseLock(TLOCK_OWS);
×
92
    msSetError(MS_HTTPERR, "Libcurl initialization failed.", "msHTTPInit()");
×
93
    return MS_FAILURE;
×
94
  }
95

96
  gbCurlInitialized = MS_TRUE;
36✔
97

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

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

111
  gbCurlInitialized = MS_FALSE;
2,553✔
112
  msReleaseLock(TLOCK_OWS);
2,553✔
113
}
2,553✔
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) {
48✔
123
  int i;
124

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

144
    pasReqInfo[i].debug = MS_FALSE;
74✔
145

146
    pasReqInfo[i].curl_handle = NULL;
74✔
147
    pasReqInfo[i].fp = NULL;
74✔
148
    pasReqInfo[i].result_data = NULL;
74✔
149
    pasReqInfo[i].result_size = 0;
74✔
150
    pasReqInfo[i].result_buf_size = 0;
74✔
151
  }
152
}
48✔
153

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

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

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

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

177
    if (pasReqInfo[i].pszContentType)
65✔
178
      free(pasReqInfo[i].pszContentType);
45✔
179
    pasReqInfo[i].pszContentType = NULL;
65✔
180

181
    if (pasReqInfo[i].pszErrBuf)
65✔
182
      free(pasReqInfo[i].pszErrBuf);
42✔
183
    pasReqInfo[i].pszErrBuf = NULL;
65✔
184

185
    if (pasReqInfo[i].pszUserAgent)
65✔
186
      free(pasReqInfo[i].pszUserAgent);
42✔
187
    pasReqInfo[i].pszUserAgent = NULL;
65✔
188

189
    if (pasReqInfo[i].pszHTTPCookieData)
65✔
190
      free(pasReqInfo[i].pszHTTPCookieData);
×
191
    pasReqInfo[i].pszHTTPCookieData = NULL;
65✔
192

193
    pasReqInfo[i].curl_handle = NULL;
65✔
194

195
    free(pasReqInfo[i].result_data);
65✔
196
    pasReqInfo[i].result_data = NULL;
65✔
197
    pasReqInfo[i].result_size = 0;
65✔
198
    pasReqInfo[i].result_buf_size = 0;
65✔
199
  }
200
}
48✔
201

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

216
  psReq = (httpRequestObj *)reqInfo;
217

218
  if (psReq->debug) {
270✔
219
    msDebug("msHTTPWriteFct(id=%d, %d bytes)\n", psReq->nLayerId,
25✔
220
            (int)(size * nmemb));
221
  }
222

223
  if (psReq->nMaxBytes > 0 &&
270✔
224
      (psReq->result_size + size * nmemb) > (size_t)psReq->nMaxBytes) {
150✔
225
    msSetError(MS_HTTPERR,
1✔
226
               "Requested transfer larger than configured maximum %d.",
227
               "msHTTPWriteFct()", psReq->nMaxBytes);
228
    return -1;
1✔
229
  }
230
  /* Case where we are writing to a disk file. */
231
  if (psReq->fp != NULL) {
269✔
232
    psReq->result_size += size * nmemb;
152✔
233
    return fwrite(buffer, size, nmemb, psReq->fp);
152✔
234
  }
235

236
  /* Case where we build up the result in memory */
237
  else {
238
    if (psReq->result_data == NULL) {
117✔
239
      psReq->result_buf_size = size * nmemb + 10000;
26✔
240
      psReq->result_data = (char *)msSmallMalloc(psReq->result_buf_size);
26✔
241
    } else if (psReq->result_size + nmemb * size >
91✔
242
               (size_t)psReq->result_buf_size) {
91✔
243
      psReq->result_buf_size = psReq->result_size + nmemb * size + 10000;
25✔
244
      psReq->result_data =
25✔
245
          (char *)msSmallRealloc(psReq->result_data, psReq->result_buf_size);
25✔
246
    }
247

248
    if (psReq->result_data == NULL) {
117✔
249
      msSetError(MS_HTTPERR, "Unable to grow HTTP result buffer to size %d.",
×
250
                 "msHTTPWriteFct()", psReq->result_buf_size);
251
      psReq->result_buf_size = 0;
×
252
      psReq->result_size = 0;
×
253
      return -1;
×
254
    }
255

256
    memcpy(psReq->result_data + psReq->result_size, buffer, size * nmemb);
117✔
257
    psReq->result_size += size * nmemb;
117✔
258

259
    return size * nmemb;
117✔
260
  }
261
}
262

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

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

300
  const char *pszTmp;
301
  char *pszProxyHost = NULL;
302
  long nProxyPort = 0;
303
  char *pszProxyUsername = NULL, *pszProxyPassword = NULL;
304
  char *pszHttpAuthUsername = NULL, *pszHttpAuthPassword = NULL;
305
  enum MS_HTTP_AUTH_TYPE eHttpAuthType = MS_BASIC;
306
  enum MS_HTTP_AUTH_TYPE eProxyAuthType = MS_BASIC;
307
  enum MS_HTTP_PROXY_TYPE eProxyType = MS_HTTP;
308

309
  /* ------------------------------------------------------------------
310
   * Check for authentication and proxying metadata. If the metadata is not
311
   * found in the layer metadata, check the map-level metadata.
312
   * ------------------------------------------------------------------ */
313
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces, "proxy_host")) !=
31✔
314
      NULL) {
315
    pszProxyHost = msStrdup(pszTmp);
×
316
  }
317

318
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces, "proxy_port")) !=
31✔
319
      NULL) {
320
    nProxyPort = atol(pszTmp);
321
  }
322

323
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces, "proxy_type")) !=
31✔
324
      NULL) {
325

326
    if (strcasecmp(pszTmp, "HTTP") == 0)
×
327
      eProxyType = MS_HTTP;
328
    else if (strcasecmp(pszTmp, "SOCKS5") == 0)
×
329
      eProxyType = MS_SOCKS5;
330
    else {
331
      msSetError(MS_WMSERR, "Invalid proxy_type metadata '%s' specified",
×
332
                 "msHTTPAuthProxySetup()", pszTmp);
333
      return MS_FAILURE;
×
334
    }
335
  }
336

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

356
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
31✔
357
                                     "proxy_username")) != NULL) {
358
    pszProxyUsername = msStrdup(pszTmp);
×
359
  }
360

361
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
31✔
362
                                     "proxy_password")) != NULL) {
363
    pszProxyPassword = msDecryptStringTokens(map, pszTmp);
×
364
    if (pszProxyPassword == NULL) {
×
365
      msFree(pszProxyHost);
×
366
      msFree(pszProxyUsername);
×
367
      return (MS_FAILURE); /* An error should already have been produced */
×
368
    }
369
  }
370

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

390
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
31✔
391
                                     "auth_username")) != NULL) {
392
    pszHttpAuthUsername = msStrdup(pszTmp);
×
393
  }
394

395
  if ((pszTmp = msOWSLookupMetadata2(lyrmd, mapmd, namespaces,
31✔
396
                                     "auth_password")) != NULL) {
397
    pszHttpAuthPassword = msDecryptStringTokens(map, pszTmp);
×
398
    if (pszHttpAuthPassword == NULL) {
×
399
      msFree(pszHttpAuthUsername);
×
400
      msFree(pszProxyHost);
×
401
      msFree(pszProxyUsername);
×
402
      msFree(pszProxyPassword);
×
403
      return (MS_FAILURE); /* An error should already have been produced */
×
404
    }
405
  }
406

407
  pasReqInfo[numRequests].pszProxyAddress = pszProxyHost;
31✔
408
  pasReqInfo[numRequests].nProxyPort = nProxyPort;
31✔
409
  pasReqInfo[numRequests].eProxyType = eProxyType;
31✔
410
  pasReqInfo[numRequests].eProxyAuthType = eProxyAuthType;
31✔
411
  pasReqInfo[numRequests].pszProxyUsername = pszProxyUsername;
31✔
412
  pasReqInfo[numRequests].pszProxyPassword = pszProxyPassword;
31✔
413
  pasReqInfo[numRequests].eHttpAuthType = eHttpAuthType;
31✔
414
  pasReqInfo[numRequests].pszHttpUsername = pszHttpAuthUsername;
31✔
415
  pasReqInfo[numRequests].pszHttpPassword = pszHttpAuthPassword;
31✔
416

417
  return MS_SUCCESS;
31✔
418
}
419

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

441
  if (numRequests == 0)
48✔
442
    return MS_SUCCESS; /* Nothing to do */
443

444
  if (!gbCurlInitialized)
48✔
445
    msHTTPInit();
36✔
446

447
  /* Establish the timeout (seconds) for how long we are going to wait
448
   * for a response.
449
   * We use the longest timeout value in the array of requests
450
   */
451
  nTimeout = pasReqInfo[0].nTimeout;
48✔
452
  for (i = 0; i < numRequests; i++) {
96✔
453
    if (pasReqInfo[i].nTimeout > nTimeout)
48✔
454
      nTimeout = pasReqInfo[i].nTimeout;
455

456
    if (pasReqInfo[i].debug)
48✔
457
      debug = MS_TRUE; /* For the download loop */
458
  }
459

460
  if (nTimeout <= 0)
48✔
461
    nTimeout = 30;
462

463
  /* Check if we've got a CURL_CA_BUNDLE env. var.
464
   * If set then the value is the full path to the ca-bundle.crt file
465
   * e.g. CURL_CA_BUNDLE=/usr/local/share/curl/curl-ca-bundle.crt
466
   */
467
  pszCurlCABundle = CPLGetConfigOption("CURL_CA_BUNDLE", NULL);
48✔
468

469
  if (debug) {
48✔
470
    msDebug("HTTP: Starting to prepare HTTP requests.\n");
11✔
471
    if (pszCurlCABundle)
11✔
472
      msDebug("Using CURL_CA_BUNDLE=%s\n", pszCurlCABundle);
×
473
  }
474

475
  const char *pszHttpVersion = CPLGetConfigOption("CURL_HTTP_VERSION", NULL);
48✔
476

477
  /* Alloc a curl-multi handle, and add a curl-easy handle to it for each
478
   * file to download.
479
   */
480
  multi_handle = curl_multi_init();
48✔
481
  if (multi_handle == NULL) {
48✔
482
    msSetError(MS_HTTPERR, "curl_multi_init() failed.",
×
483
               "msHTTPExecuteRequests()");
484
    return (MS_FAILURE);
×
485
  }
486

487
  for (i = 0; i < numRequests; i++) {
96✔
488
    CURL *http_handle;
489
    FILE *fp;
490

491
    if (pasReqInfo[i].pszGetUrl == NULL) {
48✔
492
      msSetError(MS_HTTPERR, "URL or output file parameter missing.",
×
493
                 "msHTTPExecuteRequests()");
494
      return (MS_FAILURE);
×
495
    }
496

497
    if (pasReqInfo[i].debug) {
48✔
498
      msDebug("HTTP request: id=%d, %s\n", pasReqInfo[i].nLayerId,
11✔
499
              pasReqInfo[i].pszGetUrl);
500
    }
501

502
    /* Reset some members */
503
    pasReqInfo[i].nStatus = 0;
48✔
504
    if (pasReqInfo[i].pszContentType)
48✔
505
      free(pasReqInfo[i].pszContentType);
×
506
    pasReqInfo[i].pszContentType = NULL;
48✔
507

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

523
    /* Alloc curl handle */
524
    http_handle = curl_easy_init();
42✔
525
    if (http_handle == NULL) {
42✔
526
      msSetError(MS_HTTPERR, "curl_easy_init() failed.",
×
527
                 "msHTTPExecuteRequests()");
528
      return (MS_FAILURE);
×
529
    }
530

531
    pasReqInfo[i].curl_handle = http_handle;
42✔
532

533
    /* set URL, note that curl keeps only a ref to our string buffer */
534
    unchecked_curl_easy_setopt(http_handle, CURLOPT_URL,
42✔
535
                               pasReqInfo[i].pszGetUrl);
536

537
#if CURL_AT_LEAST_VERSION(7, 85, 0)
538
    unchecked_curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS_STR,
539
                               "http,https");
540
#else
541
    unchecked_curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS,
42✔
542
                               CURLPROTO_HTTP | CURLPROTO_HTTPS);
543
#endif
544

545
    if (pszHttpVersion && strcmp(pszHttpVersion, "1.0") == 0)
42✔
546
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
×
547
                                 CURL_HTTP_VERSION_1_0);
548
    else if (pszHttpVersion && strcmp(pszHttpVersion, "1.1") == 0)
42✔
549
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
32✔
550
                                 CURL_HTTP_VERSION_1_1);
551

552
    /* Set User-Agent (auto-generate if not set by caller */
553
    if (pasReqInfo[i].pszUserAgent == NULL) {
42✔
554
      curl_version_info_data *psCurlVInfo;
555

556
      psCurlVInfo = curl_version_info(CURLVERSION_NOW);
42✔
557

558
      pasReqInfo[i].pszUserAgent = (char *)msSmallMalloc(100 * sizeof(char));
42✔
559

560
      if (pasReqInfo[i].pszUserAgent) {
42✔
561
        sprintf(pasReqInfo[i].pszUserAgent, "MapServer/%s libcurl/%d.%d.%d",
42✔
562
                MS_VERSION, psCurlVInfo->version_num / 0x10000 & 0xff,
42✔
563
                psCurlVInfo->version_num / 0x100 & 0xff,
42✔
564
                psCurlVInfo->version_num & 0xff);
42✔
565
      }
566
    }
567
    if (pasReqInfo[i].pszUserAgent) {
42✔
568
      unchecked_curl_easy_setopt(http_handle, CURLOPT_USERAGENT,
42✔
569
                                 pasReqInfo[i].pszUserAgent);
570
    }
571

572
    /* Enable following redirections.  Requires libcurl 7.10.1 at least */
573
    unchecked_curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1);
42✔
574
    unchecked_curl_easy_setopt(http_handle, CURLOPT_MAXREDIRS, 10);
42✔
575

576
    /* Set timeout.*/
577
    unchecked_curl_easy_setopt(http_handle, CURLOPT_TIMEOUT, nTimeout);
42✔
578

579
    /* Pass CURL_CA_BUNDLE if set */
580
    if (pszCurlCABundle)
42✔
581
      unchecked_curl_easy_setopt(http_handle, CURLOPT_CAINFO, pszCurlCABundle);
×
582

583
    /* Set proxying settings */
584
    if (pasReqInfo[i].pszProxyAddress != NULL &&
42✔
585
        strlen(pasReqInfo[i].pszProxyAddress) > 0) {
×
586
      long nProxyType = CURLPROXY_HTTP;
587

588
      unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXY,
×
589
                                 pasReqInfo[i].pszProxyAddress);
590

591
      if (pasReqInfo[i].nProxyPort > 0 && pasReqInfo[i].nProxyPort < 65535) {
×
592
        unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYPORT,
×
593
                                   pasReqInfo[i].nProxyPort);
594
      }
595

596
      switch (pasReqInfo[i].eProxyType) {
×
597
      case MS_HTTP:
598
        nProxyType = CURLPROXY_HTTP;
599
        break;
600
      case MS_SOCKS5:
×
601
        nProxyType = CURLPROXY_SOCKS5;
602
        break;
×
603
      }
604
      unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYTYPE, nProxyType);
×
605

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

626
        snprintf(szUsernamePasswd, 127, "%s:%s", pasReqInfo[i].pszProxyUsername,
×
627
                 pasReqInfo[i].pszProxyPassword);
628
        unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYUSERPWD,
×
629
                                   szUsernamePasswd);
630
      }
631
    }
632

633
    /* Set HTTP Authentication settings */
634
    if (pasReqInfo[i].pszHttpUsername != NULL &&
42✔
635
        pasReqInfo[i].pszHttpPassword != NULL &&
×
636
        strlen(pasReqInfo[i].pszHttpUsername) > 0 &&
×
637
        strlen(pasReqInfo[i].pszHttpPassword) > 0) {
×
638
      char szUsernamePasswd[128];
639
      long nHttpAuthType = CURLAUTH_BASIC;
640

641
      snprintf(szUsernamePasswd, 127, "%s:%s", pasReqInfo[i].pszHttpUsername,
642
               pasReqInfo[i].pszHttpPassword);
643
      unchecked_curl_easy_setopt(http_handle, CURLOPT_USERPWD,
×
644
                                 szUsernamePasswd);
645

646
      nHttpAuthType = msGetCURLAuthType(pasReqInfo[i].eHttpAuthType);
×
647
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH, nHttpAuthType);
×
648
    }
649

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

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

666
      pasReqInfo[i].fp = fp;
14✔
667
    }
668

669
    /* coverity[bad_sizeof] */
670
    unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEDATA,
42✔
671
                               &(pasReqInfo[i]));
672
    unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION,
42✔
673
                               msHTTPWriteFct);
674

675
    /* Provide a buffer where libcurl can write human readable error msgs
676
     */
677
    if (pasReqInfo[i].pszErrBuf == NULL)
42✔
678
      pasReqInfo[i].pszErrBuf =
42✔
679
          (char *)msSmallMalloc((CURL_ERROR_SIZE + 1) * sizeof(char));
42✔
680
    pasReqInfo[i].pszErrBuf[0] = '\0';
42✔
681

682
    unchecked_curl_easy_setopt(http_handle, CURLOPT_ERRORBUFFER,
42✔
683
                               pasReqInfo[i].pszErrBuf);
684

685
    pasReqInfo[i].curl_headers = NULL;
42✔
686

687
    if (pasReqInfo[i].pszPostRequest != NULL) {
42✔
688
      char szBuf[100];
689

690
      snprintf(szBuf, 100, "Content-Type: %s",
3✔
691
               pasReqInfo[i].pszPostContentType);
692
      pasReqInfo[i].curl_headers = curl_slist_append(NULL, szBuf);
3✔
693

694
      unchecked_curl_easy_setopt(http_handle, CURLOPT_POST, 1);
3✔
695
      unchecked_curl_easy_setopt(http_handle, CURLOPT_POSTFIELDS,
3✔
696
                                 pasReqInfo[i].pszPostRequest);
697
      if (debug) {
3✔
698
        msDebug("HTTP: POST = %s", pasReqInfo[i].pszPostRequest);
×
699
      }
700
      unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPHEADER,
3✔
701
                                 pasReqInfo[i].curl_headers);
702
    }
703

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

718
      /* Set the Curl option to send Cookie */
719
      unchecked_curl_easy_setopt(http_handle, CURLOPT_COOKIE,
×
720
                                 pasReqInfo[i].pszHTTPCookieData);
721
    }
722

723
    /* Add to multi handle */
724
    curl_multi_add_handle(multi_handle, http_handle);
42✔
725
  }
726

727
  if (debug) {
48✔
728
    msDebug("HTTP: Before download loop\n");
11✔
729
  }
730

731
  /* DOWNLOAD LOOP ... inspired from multi-double.c example */
732

733
  /* we start some action by calling perform right away */
734
  while (CURLM_CALL_MULTI_PERFORM ==
48✔
735
         curl_multi_perform(multi_handle, &still_running))
48✔
736
    ;
737

738
  while (still_running) {
560✔
739
    struct timeval timeout;
740
    int rc; /* select() return code */
741

742
    fd_set fdread;
743
    fd_set fdwrite;
744
    fd_set fdexcep;
745
    int maxfd;
746

747
    FD_ZERO(&fdread);
512✔
748
    FD_ZERO(&fdwrite);
512✔
749
    FD_ZERO(&fdexcep);
512✔
750

751
    /* set a suitable timeout to play around with */
752
    timeout.tv_sec = 0;
512✔
753
    timeout.tv_usec = 100000;
512✔
754

755
    /* get file descriptors from the transfers */
756
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
512✔
757

758
    rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
512✔
759

760
    switch (rc) {
512✔
761
    case -1:
762
      /* select error */
763

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

782
  if (debug)
48✔
783
    msDebug("HTTP: After download loop\n");
11✔
784

785
  /* Scan message stack from CURL and report fatal errors*/
786

787
  while ((curl_msg = curl_multi_info_read(multi_handle, &num_msgs)) != NULL) {
90✔
788
    httpRequestObj *psReq = NULL;
789

790
    if (curl_msg->msg == CURLMSG_DONE && curl_msg->data.result != CURLE_OK) {
42✔
791
      /* Something went wrong with this transfer... report error */
792

793
      for (i = 0; i < numRequests; i++) {
3✔
794
        if (pasReqInfo[i].curl_handle == curl_msg->easy_handle) {
3✔
795
          psReq = &(pasReqInfo[i]);
796
          break;
797
        }
798
      }
799

800
      if (psReq != NULL) {
3✔
801
        /* Record error code in nStatus as a negative value */
802
        psReq->nStatus = -curl_msg->data.result;
3✔
803
      }
804
    }
805
  }
806

807
  if (debug) {
48✔
808
    /* Print a msDebug header for timings reported in the loop below */
809
    msDebug("msHTTPExecuteRequests() timing summary per layer (connect_time + "
11✔
810
            "time_to_first_packet + download_time = total_time in seconds)\n");
811
  }
812

813
  /* Check status of all requests, close files, report errors and cleanup
814
   * handles
815
   */
816
  for (i = 0; i < numRequests; i++) {
96✔
817
    httpRequestObj *psReq;
818
    CURL *http_handle;
819
    long lVal = 0;
48✔
820

821
    psReq = &(pasReqInfo[i]);
48✔
822

823
    if (psReq->nStatus == 242)
48✔
824
      continue; /* Nothing to do here, this file was in cache already */
6✔
825

826
    if (psReq->fp)
42✔
827
      fclose(psReq->fp);
14✔
828
    psReq->fp = NULL;
42✔
829

830
    http_handle = (CURL *)(psReq->curl_handle);
42✔
831

832
    if (psReq->nStatus == 0 &&
81✔
833
        curl_easy_getinfo(http_handle, CURLINFO_HTTP_CODE, &lVal) == CURLE_OK) {
39✔
834
      char *pszContentType = NULL;
39✔
835

836
      psReq->nStatus = lVal;
39✔
837

838
      /* Fetch content type of response */
839
      if (curl_easy_getinfo(http_handle, CURLINFO_CONTENT_TYPE,
39✔
840
                            &pszContentType) == CURLE_OK &&
39✔
841
          pszContentType != NULL) {
39✔
842
        psReq->pszContentType = msStrdup(pszContentType);
39✔
843
      }
844
    }
845

846
    if (!MS_HTTP_SUCCESS(psReq->nStatus)) {
42✔
847
      /* Set status to MS_DONE to indicate that transfers were  */
848
      /* completed but may not be successful */
849
      nStatus = MS_DONE;
850

851
      if (psReq->nStatus == -(CURLE_OPERATION_TIMEOUTED)) {
4✔
852
        /* Timeout isn't a fatal error */
853
        if (psReq->debug)
×
854
          msDebug("HTTP: TIMEOUT of %d seconds exceeded for %s\n", nTimeout,
×
855
                  psReq->pszGetUrl);
856

857
        msSetError(MS_HTTPERR, "HTTP: TIMEOUT of %d seconds exceeded for %s\n",
×
858
                   "msHTTPExecuteRequests()", nTimeout, psReq->pszGetUrl);
859

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

867
        if (psReq->debug)
1✔
868
          msDebug("HTTP: HTTP GET request failed with status %d (%s)"
×
869
                  " for %s\n",
870
                  psReq->nStatus, psReq->pszErrBuf, psReq->pszGetUrl);
871

872
        msSetError(MS_HTTPERR,
1✔
873
                   "HTTP GET request failed with status %d (%s) "
874
                   "for %s",
875
                   "msHTTPExecuteRequests()", psReq->nStatus, psReq->pszErrBuf,
876
                   psReq->pszGetUrl);
877
      } else {
878
        /* Got a curl error */
879

880
        errorObj *error = msGetErrorObj();
3✔
881
        if (psReq->debug)
3✔
882
          msDebug("HTTP: request failed with curl error "
×
883
                  "code %d (%s) for %s",
884
                  -psReq->nStatus, psReq->pszErrBuf, psReq->pszGetUrl);
×
885

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

897
    /* Report download times foreach handle, in debug mode */
898
    if (psReq->debug) {
42✔
899
      double dConnectTime = 0.0, dTotalTime = 0.0, dStartTfrTime = 0.0;
11✔
900

901
      curl_easy_getinfo(http_handle, CURLINFO_CONNECT_TIME, &dConnectTime);
11✔
902
      curl_easy_getinfo(http_handle, CURLINFO_STARTTRANSFER_TIME,
11✔
903
                        &dStartTfrTime);
904
      curl_easy_getinfo(http_handle, CURLINFO_TOTAL_TIME, &dTotalTime);
11✔
905
      /* STARTTRANSFER_TIME includes CONNECT_TIME, but TOTAL_TIME
906
       * doesn't, so we need to add it.
907
       */
908
      dTotalTime += dConnectTime;
11✔
909

910
      msDebug("Layer %d: %.3f + %.3f + %.3f = %.3fs\n", psReq->nLayerId,
11✔
911
              dConnectTime, dStartTfrTime - dConnectTime,
912
              dTotalTime - dStartTfrTime, dTotalTime);
913
    }
914

915
    /* Cleanup this handle */
916
    unchecked_curl_easy_setopt(http_handle, CURLOPT_URL, "");
42✔
917
    curl_multi_remove_handle(multi_handle, http_handle);
42✔
918
    curl_easy_cleanup(http_handle);
42✔
919
    psReq->curl_handle = NULL;
42✔
920
    curl_slist_free_all(psReq->curl_headers); // free the header list
42✔
921
  }
922

923
  /* Cleanup multi handle, each handle had to be cleaned up individually */
924
  curl_multi_cleanup(multi_handle);
48✔
925

926
  return nStatus;
48✔
927
}
928

929
/**********************************************************************
930
 *                          msHTTPGetFile()
931
 *
932
 * Wrapper to call msHTTPExecuteRequests() for a single file.
933
 **********************************************************************/
934
int msHTTPGetFile(const char *pszGetUrl, const char *pszOutputFile,
17✔
935
                  int *pnHTTPStatus, int nTimeout, int bCheckLocalCache,
936
                  int bDebug, int nMaxBytes) {
937
  httpRequestObj *pasReqInfo;
938

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

947
  msHTTPInitRequestObj(pasReqInfo, 2);
17✔
948

949
  pasReqInfo[0].pszGetUrl = msStrdup(pszGetUrl);
17✔
950
  pasReqInfo[0].pszOutputFile = msStrdup(pszOutputFile);
17✔
951
  pasReqInfo[0].debug = (char)bDebug;
17✔
952
  pasReqInfo[0].nTimeout = nTimeout;
17✔
953
  pasReqInfo[0].nMaxBytes = nMaxBytes;
17✔
954

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

964
  *pnHTTPStatus = pasReqInfo[0].nStatus;
13✔
965

966
  msHTTPFreeRequestObj(pasReqInfo, 2);
13✔
967
  free(pasReqInfo);
13✔
968

969
  return MS_SUCCESS;
13✔
970
}
971

972
#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