Coverage Report

Created: 2025-06-09 08:44

/src/gdal/frmts/http/httpdriver.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  WCS Client Driver
4
 * Purpose:  Implementation of an HTTP fetching driver.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2007, Frank Warmerdam
9
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_error_internal.h"
15
#include "cpl_string.h"
16
#include "cpl_http.h"
17
#include "gdal_frmts.h"
18
#include "gdal_pam.h"
19
20
static std::string SanitizeDispositionFilename(const std::string &osVal)
21
0
{
22
0
    std::string osRet(osVal);
23
0
    if (!osRet.empty() && osRet[0] == '"')
24
0
    {
25
0
        const auto nEnd = osRet.find('"', 1);
26
0
        if (nEnd != std::string::npos)
27
0
            return osRet.substr(1, nEnd - 1);
28
0
    }
29
0
    return osRet;
30
0
}
31
32
/************************************************************************/
33
/*               HTTPFetchContentDispositionFilename()                 */
34
/************************************************************************/
35
36
static std::string HTTPFetchContentDispositionFilename(char **papszHeaders)
37
0
{
38
0
    char **papszIter = papszHeaders;
39
0
    while (papszIter && *papszIter)
40
0
    {
41
        /* For multipart, we have in raw format, but without end-of-line
42
         * characters */
43
0
        if (STARTS_WITH(*papszIter,
44
0
                        "Content-Disposition: attachment; filename="))
45
0
        {
46
0
            return SanitizeDispositionFilename(*papszIter + 42);
47
0
        }
48
        /* For single part, the headers are in KEY=VAL format, but with e-o-l
49
         * ... */
50
0
        else if (STARTS_WITH(*papszIter,
51
0
                             "Content-Disposition=attachment; filename="))
52
0
        {
53
0
            char *pszVal = (*papszIter + 41);
54
0
            char *pszEOL = strchr(pszVal, '\r');
55
0
            if (pszEOL)
56
0
                *pszEOL = 0;
57
0
            pszEOL = strchr(pszVal, '\n');
58
0
            if (pszEOL)
59
0
                *pszEOL = 0;
60
0
            return SanitizeDispositionFilename(pszVal);
61
0
        }
62
0
        papszIter++;
63
0
    }
64
0
    return std::string();
65
0
}
66
67
/************************************************************************/
68
/*                              HTTPOpen()                              */
69
/************************************************************************/
70
71
static GDALDataset *HTTPOpen(GDALOpenInfo *poOpenInfo)
72
73
863k
{
74
863k
    if (poOpenInfo->nHeaderBytes != 0)
75
69.3k
        return nullptr;
76
77
793k
    if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "http:") &&
78
793k
        !STARTS_WITH_CI(poOpenInfo->pszFilename, "https:") &&
79
793k
        !STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp:"))
80
792k
        return nullptr;
81
82
    /* -------------------------------------------------------------------- */
83
    /*      Fetch the result.                                               */
84
    /* -------------------------------------------------------------------- */
85
868
    CPLErrorReset();
86
868
    CPLHTTPResult *psResult = CPLHTTPFetch(poOpenInfo->pszFilename, nullptr);
87
88
    /* -------------------------------------------------------------------- */
89
    /*      Try to handle errors.                                           */
90
    /* -------------------------------------------------------------------- */
91
868
    if (psResult == nullptr || psResult->nDataLen == 0 ||
92
868
        CPLGetLastErrorNo() != 0)
93
868
    {
94
868
        CPLHTTPDestroyResult(psResult);
95
868
        return nullptr;
96
868
    }
97
98
    /* -------------------------------------------------------------------- */
99
    /*      Create a memory file from the result.                           */
100
    /* -------------------------------------------------------------------- */
101
0
    std::string osFilename =
102
0
        HTTPFetchContentDispositionFilename(psResult->papszHeaders);
103
0
    if (osFilename.empty())
104
0
    {
105
0
        osFilename = CPLGetFilename(poOpenInfo->pszFilename);
106
        /* If we have special characters, let's default to a fixed name */
107
0
        if (strchr(osFilename.c_str(), '?') || strchr(osFilename.c_str(), '&'))
108
0
            osFilename = "file.dat";
109
0
    }
110
111
    // If changing the _gdal_http_ marker, change jpgdataset.cpp that tests for it
112
0
    const CPLString osResultFilename = VSIMemGenerateHiddenFilename(
113
0
        std::string("_gdal_http_").append(osFilename).c_str());
114
115
0
    VSILFILE *fp = VSIFileFromMemBuffer(osResultFilename, psResult->pabyData,
116
0
                                        psResult->nDataLen, TRUE);
117
118
0
    if (fp == nullptr)
119
0
        return nullptr;
120
121
0
    VSIFCloseL(fp);
122
123
    /* -------------------------------------------------------------------- */
124
    /*      Steal the memory buffer from HTTP result before destroying      */
125
    /*      it.                                                             */
126
    /* -------------------------------------------------------------------- */
127
0
    psResult->pabyData = nullptr;
128
0
    psResult->nDataLen = 0;
129
0
    psResult->nDataAlloc = 0;
130
131
0
    CPLHTTPDestroyResult(psResult);
132
133
0
    CPLStringList aosOpenOptions;
134
0
    for (const char *pszStr :
135
0
         cpl::Iterate(const_cast<CSLConstList>(poOpenInfo->papszOpenOptions)))
136
0
    {
137
0
        if (STARTS_WITH_CI(pszStr, "NATIVE_DATA="))
138
0
        {
139
            // Avoid warning with "ogr2ogr out http://example.com/in.gpkg"
140
0
            aosOpenOptions.push_back(std::string("@").append(pszStr).c_str());
141
0
        }
142
0
        else
143
0
        {
144
0
            aosOpenOptions.push_back(pszStr);
145
0
        }
146
0
    }
147
148
    /* -------------------------------------------------------------------- */
149
    /*      Try opening this result as a gdaldataset.                       */
150
    /* -------------------------------------------------------------------- */
151
    /* suppress errors as not all drivers support /vsimem */
152
153
0
    GDALDataset *poDS;
154
0
    CPLErrorAccumulator oErrorAccumulator;
155
0
    {
156
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
157
0
        auto oAccumulator = oErrorAccumulator.InstallForCurrentScope();
158
0
        CPL_IGNORE_RET_VAL(oAccumulator);
159
0
        poDS = GDALDataset::Open(
160
0
            osResultFilename, poOpenInfo->nOpenFlags & ~GDAL_OF_SHARED,
161
0
            poOpenInfo->papszAllowedDrivers, aosOpenOptions.List(), nullptr);
162
0
    }
163
164
    // Re-emit silenced errors if open was successful
165
0
    if (poDS)
166
0
    {
167
0
        oErrorAccumulator.ReplayErrors();
168
0
    }
169
170
    // The JP2OpenJPEG driver may need to reopen the file, hence this special
171
    // behavior
172
0
    if (poDS != nullptr && poDS->GetDriver() != nullptr &&
173
0
        EQUAL(poDS->GetDriver()->GetDescription(), "JP2OpenJPEG"))
174
0
    {
175
0
        poDS->MarkSuppressOnClose();
176
0
        return poDS;
177
0
    }
178
179
    /* -------------------------------------------------------------------- */
180
    /*      If opening it in memory didn't work, perhaps we need to         */
181
    /*      write to a temp file on disk?                                   */
182
    /* -------------------------------------------------------------------- */
183
0
    if (poDS == nullptr)
184
0
    {
185
0
        CPLString osTempFilename;
186
187
#ifdef _WIN32
188
        const std::string osPath =
189
            CPLGetPathSafe(CPLGenerateTempFilenameSafe(NULL).c_str());
190
#else
191
0
        const std::string osPath = "/tmp";
192
0
#endif
193
0
        osTempFilename = CPLFormFilenameSafe(
194
0
            osPath.c_str(), CPLGetFilename(osResultFilename), nullptr);
195
0
        if (CPLCopyFile(osTempFilename, osResultFilename) != 0)
196
0
        {
197
0
            CPLError(CE_Failure, CPLE_OpenFailed,
198
0
                     "Failed to create temporary file:%s",
199
0
                     osTempFilename.c_str());
200
0
        }
201
0
        else
202
0
        {
203
0
            poDS = GDALDataset::Open(osTempFilename,
204
0
                                     poOpenInfo->nOpenFlags & ~GDAL_OF_SHARED,
205
0
                                     poOpenInfo->papszAllowedDrivers,
206
0
                                     aosOpenOptions.List(), nullptr);
207
0
            if (VSIUnlink(osTempFilename) != 0 && poDS != nullptr)
208
0
                poDS->MarkSuppressOnClose(); /* VSIUnlink() may not work on
209
                                                windows */
210
0
            if (poDS && strcmp(poDS->GetDescription(), osTempFilename) == 0)
211
0
                poDS->SetDescription(poOpenInfo->pszFilename);
212
0
        }
213
0
    }
214
0
    else if (strcmp(poDS->GetDescription(), osResultFilename) == 0)
215
0
        poDS->SetDescription(poOpenInfo->pszFilename);
216
217
    /* -------------------------------------------------------------------- */
218
    /*      Release our hold on the vsi memory file, though if it is        */
219
    /*      held open by a dataset it will continue to exist till that      */
220
    /*      lets it go.                                                     */
221
    /* -------------------------------------------------------------------- */
222
0
    VSIUnlink(osResultFilename);
223
224
0
    return poDS;
225
0
}
226
227
/************************************************************************/
228
/*                         GDALRegister_HTTP()                          */
229
/************************************************************************/
230
231
void GDALRegister_HTTP()
232
233
24
{
234
24
    if (GDALGetDriverByName("HTTP") != nullptr)
235
0
        return;
236
237
24
    GDALDriver *poDriver = new GDALDriver();
238
239
24
    poDriver->SetDescription("HTTP");
240
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
241
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
242
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "HTTP Fetching Wrapper");
243
244
24
    poDriver->pfnOpen = HTTPOpen;
245
246
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
247
24
}