Line data Source code
1 : /**********************************************************************
2 : * Project: CPL - Common Portability Library
3 : * Purpose: Microsoft Azure Storage Blob routines
4 : * Author: Even Rouault <even.rouault at spatialys.com>
5 : *
6 : **********************************************************************
7 : * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
8 : *
9 : * Permission is hereby granted, free of charge, to any person obtaining a
10 : * copy of this software and associated documentation files (the "Software"),
11 : * to deal in the Software without restriction, including without limitation
12 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 : * and/or sell copies of the Software, and to permit persons to whom the
14 : * Software is furnished to do so, subject to the following conditions:
15 : *
16 : * The above copyright notice and this permission notice shall be included
17 : * in all copies or substantial portions of the Software.
18 : *
19 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 : * DEALINAzureBlob IN THE SOFTWARE.
26 : ****************************************************************************/
27 :
28 : #include "cpl_azure.h"
29 : #include "cpl_json.h"
30 : #include "cpl_minixml.h"
31 : #include "cpl_vsi_error.h"
32 : #include "cpl_sha256.h"
33 : #include "cpl_time.h"
34 : #include "cpl_http.h"
35 : #include "cpl_multiproc.h"
36 : #include "cpl_vsi_virtual.h"
37 :
38 : #include <mutex>
39 :
40 : //! @cond Doxygen_Suppress
41 :
42 : #ifdef HAVE_CURL
43 :
44 : /************************************************************************/
45 : /* RemoveTrailingSlash() */
46 : /************************************************************************/
47 :
48 572 : static std::string RemoveTrailingSlash(const std::string &osStr)
49 : {
50 572 : std::string osRet(osStr);
51 572 : if (!osRet.empty() && osRet.back() == '/')
52 1 : osRet.pop_back();
53 572 : return osRet;
54 : }
55 :
56 : /************************************************************************/
57 : /* CPLAzureGetSignature() */
58 : /************************************************************************/
59 :
60 218 : static std::string CPLAzureGetSignature(const std::string &osStringToSign,
61 : const std::string &osStorageKeyB64)
62 : {
63 :
64 : /* -------------------------------------------------------------------- */
65 : /* Compute signature. */
66 : /* -------------------------------------------------------------------- */
67 :
68 436 : std::string osStorageKeyUnbase64(osStorageKeyB64);
69 436 : int nB64Length = CPLBase64DecodeInPlace(
70 218 : reinterpret_cast<GByte *>(&osStorageKeyUnbase64[0]));
71 218 : osStorageKeyUnbase64.resize(nB64Length);
72 : #ifdef DEBUG_VERBOSE
73 : CPLDebug("AZURE", "signing key size: %d", nB64Length);
74 : #endif
75 :
76 218 : GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
77 436 : CPL_HMAC_SHA256(osStorageKeyUnbase64.c_str(), nB64Length,
78 218 : osStringToSign.c_str(), osStringToSign.size(),
79 : abySignature);
80 :
81 218 : char *pszB64Signature = CPLBase64Encode(CPL_SHA256_HASH_SIZE, abySignature);
82 218 : std::string osSignature(pszB64Signature);
83 218 : CPLFree(pszB64Signature);
84 436 : return osSignature;
85 : }
86 :
87 : /************************************************************************/
88 : /* GetAzureBlobHeaders() */
89 : /************************************************************************/
90 :
91 223 : static struct curl_slist *GetAzureBlobHeaders(
92 : const std::string &osVerb, struct curl_slist *psHeaders,
93 : const std::string &osResource,
94 : const std::map<std::string, std::string> &oMapQueryParameters,
95 : const std::string &osStorageAccount, const std::string &osStorageKeyB64,
96 : bool bIncludeMSVersion)
97 : {
98 : /* See
99 : * https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
100 : */
101 :
102 446 : std::string osDate = CPLGetConfigOption("CPL_AZURE_TIMESTAMP", "");
103 223 : if (osDate.empty())
104 : {
105 8 : osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
106 : }
107 223 : if (osStorageKeyB64.empty())
108 : {
109 9 : psHeaders = curl_slist_append(
110 : psHeaders, CPLSPrintf("x-ms-date: %s", osDate.c_str()));
111 9 : return psHeaders;
112 : }
113 :
114 428 : std::string osMsVersion("2019-12-12");
115 428 : std::map<std::string, std::string> oSortedMapMSHeaders;
116 214 : if (bIncludeMSVersion)
117 207 : oSortedMapMSHeaders["x-ms-version"] = osMsVersion;
118 214 : oSortedMapMSHeaders["x-ms-date"] = osDate;
119 : std::string osCanonicalizedHeaders(
120 : IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(oSortedMapMSHeaders,
121 428 : psHeaders, "x-ms-"));
122 :
123 428 : std::string osCanonicalizedResource;
124 214 : osCanonicalizedResource += "/" + osStorageAccount;
125 214 : osCanonicalizedResource += osResource;
126 :
127 : // We assume query parameters are in lower case and they are not repeated
128 : std::map<std::string, std::string>::const_iterator oIter =
129 214 : oMapQueryParameters.begin();
130 546 : for (; oIter != oMapQueryParameters.end(); ++oIter)
131 : {
132 332 : osCanonicalizedResource += "\n";
133 332 : osCanonicalizedResource += oIter->first;
134 332 : osCanonicalizedResource += ":";
135 332 : osCanonicalizedResource += oIter->second;
136 : }
137 :
138 428 : std::string osStringToSign;
139 214 : osStringToSign += osVerb + "\n";
140 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Encoding") + "\n";
141 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Language") + "\n";
142 : std::string osContentLength(
143 428 : CPLAWSGetHeaderVal(psHeaders, "Content-Length"));
144 214 : if (osContentLength == "0")
145 34 : osContentLength.clear(); // since x-ms-version 2015-02-21
146 214 : osStringToSign += osContentLength + "\n";
147 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-MD5") + "\n";
148 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Type") + "\n";
149 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Date") + "\n";
150 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "If-Modified-Since") + "\n";
151 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "If-Match") + "\n";
152 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "If-None-Match") + "\n";
153 : osStringToSign +=
154 214 : CPLAWSGetHeaderVal(psHeaders, "If-Unmodified-Since") + "\n";
155 214 : osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Range") + "\n";
156 214 : osStringToSign += osCanonicalizedHeaders;
157 214 : osStringToSign += osCanonicalizedResource;
158 :
159 : #ifdef DEBUG_VERBOSE
160 : CPLDebug("AZURE", "osStringToSign = '%s'", osStringToSign.c_str());
161 : #endif
162 :
163 : /* -------------------------------------------------------------------- */
164 : /* Compute signature. */
165 : /* -------------------------------------------------------------------- */
166 :
167 : std::string osAuthorization(
168 428 : "SharedKey " + osStorageAccount + ":" +
169 428 : CPLAzureGetSignature(osStringToSign, osStorageKeyB64));
170 :
171 214 : psHeaders = curl_slist_append(psHeaders,
172 : CPLSPrintf("x-ms-date: %s", osDate.c_str()));
173 214 : if (bIncludeMSVersion)
174 : {
175 207 : psHeaders = curl_slist_append(
176 : psHeaders, CPLSPrintf("x-ms-version: %s", osMsVersion.c_str()));
177 : }
178 214 : psHeaders = curl_slist_append(
179 : psHeaders, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
180 214 : return psHeaders;
181 : }
182 :
183 : /************************************************************************/
184 : /* VSIAzureBlobHandleHelper() */
185 : /************************************************************************/
186 298 : VSIAzureBlobHandleHelper::VSIAzureBlobHandleHelper(
187 : const std::string &osPathForOption, const std::string &osEndpoint,
188 : const std::string &osBucket, const std::string &osObjectKey,
189 : const std::string &osStorageAccount, const std::string &osStorageKey,
190 : const std::string &osSAS, const std::string &osAccessToken,
191 298 : bool bFromManagedIdentities)
192 : : m_osPathForOption(osPathForOption),
193 : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, osSAS)),
194 : m_osEndpoint(osEndpoint), m_osBucket(osBucket),
195 : m_osObjectKey(osObjectKey), m_osStorageAccount(osStorageAccount),
196 : m_osStorageKey(osStorageKey), m_osSAS(osSAS),
197 : m_osAccessToken(osAccessToken),
198 298 : m_bFromManagedIdentities(bFromManagedIdentities)
199 : {
200 298 : }
201 :
202 : /************************************************************************/
203 : /* ~VSIAzureBlobHandleHelper() */
204 : /************************************************************************/
205 :
206 596 : VSIAzureBlobHandleHelper::~VSIAzureBlobHandleHelper()
207 : {
208 596 : }
209 :
210 : /************************************************************************/
211 : /* AzureCSGetParameter() */
212 : /************************************************************************/
213 :
214 1067 : static std::string AzureCSGetParameter(const std::string &osStr,
215 : const char *pszKey, bool bErrorIfMissing)
216 : {
217 3201 : std::string osKey(pszKey + std::string("="));
218 1067 : size_t nPos = osStr.find(osKey);
219 1067 : if (nPos == std::string::npos)
220 : {
221 : const char *pszMsg =
222 11 : CPLSPrintf("%s missing in AZURE_STORAGE_CONNECTION_STRING", pszKey);
223 11 : if (bErrorIfMissing)
224 : {
225 0 : CPLDebug("AZURE", "%s", pszMsg);
226 0 : VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
227 : }
228 11 : return std::string();
229 : }
230 1056 : size_t nPos2 = osStr.find(";", nPos);
231 1056 : return osStr.substr(nPos + osKey.size(), nPos2 == std::string::npos
232 : ? nPos2
233 2112 : : nPos2 - nPos - osKey.size());
234 : }
235 :
236 : /************************************************************************/
237 : /* CPLAzureCachedToken */
238 : /************************************************************************/
239 :
240 : std::mutex gMutex;
241 :
242 : struct CPLAzureCachedToken
243 : {
244 : std::string osAccessToken{};
245 : GIntBig nExpiresOn = 0;
246 : };
247 :
248 : static std::map<std::string, CPLAzureCachedToken> goMapIMDSURLToCachedToken;
249 :
250 : /************************************************************************/
251 : /* GetConfigurationFromIMDSCredentials() */
252 : /************************************************************************/
253 :
254 : static bool
255 15 : GetConfigurationFromIMDSCredentials(const std::string &osPathForOption,
256 : std::string &osAccessToken)
257 : {
258 : // coverity[tainted_data]
259 : const std::string osRootURL(CPLGetConfigOption("CPL_AZURE_VM_API_ROOT_URL",
260 30 : "http://169.254.169.254"));
261 15 : if (osRootURL == "disabled")
262 1 : return false;
263 :
264 : std::string osURLResource("/metadata/identity/oauth2/"
265 : "token?api-version=2018-02-01&resource=https%"
266 28 : "3A%2F%2Fstorage.azure.com%2F");
267 14 : const char *pszObjectId = VSIGetPathSpecificOption(
268 : osPathForOption.c_str(), "AZURE_IMDS_OBJECT_ID", nullptr);
269 14 : if (pszObjectId)
270 8 : osURLResource += "&object_id=" + CPLAWSURLEncode(pszObjectId, false);
271 14 : const char *pszClientId = VSIGetPathSpecificOption(
272 : osPathForOption.c_str(), "AZURE_IMDS_CLIENT_ID", nullptr);
273 14 : if (pszClientId)
274 8 : osURLResource += "&client_id=" + CPLAWSURLEncode(pszClientId, false);
275 14 : const char *pszMsiResId = VSIGetPathSpecificOption(
276 : osPathForOption.c_str(), "AZURE_IMDS_MSI_RES_ID", nullptr);
277 14 : if (pszMsiResId)
278 8 : osURLResource += "&msi_res_id=" + CPLAWSURLEncode(pszMsiResId, false);
279 :
280 28 : std::lock_guard<std::mutex> guard(gMutex);
281 :
282 : // Look for cached token corresponding to this IMDS request URL
283 14 : auto oIter = goMapIMDSURLToCachedToken.find(osURLResource);
284 14 : if (oIter != goMapIMDSURLToCachedToken.end())
285 : {
286 10 : const auto &oCachedToken = oIter->second;
287 : time_t nCurTime;
288 10 : time(&nCurTime);
289 : // Try to reuse credentials if they are still valid, but
290 : // keep one minute of margin...
291 10 : if (nCurTime < oCachedToken.nExpiresOn - 60)
292 : {
293 9 : osAccessToken = oCachedToken.osAccessToken;
294 9 : return true;
295 : }
296 : }
297 :
298 : // Fetch credentials
299 5 : CPLStringList oResponse;
300 5 : const char *const apszOptions[] = {"HEADERS=Metadata: true", nullptr};
301 : CPLHTTPResult *psResult =
302 5 : CPLHTTPFetch((osRootURL + osURLResource).c_str(), apszOptions);
303 5 : if (psResult)
304 : {
305 5 : if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
306 : {
307 : const std::string osJSon =
308 10 : reinterpret_cast<char *>(psResult->pabyData);
309 5 : oResponse = CPLParseKeyValueJson(osJSon.c_str());
310 5 : if (oResponse.FetchNameValue("error"))
311 : {
312 0 : CPLDebug("AZURE",
313 : "Cannot retrieve managed identities credentials: %s",
314 : osJSon.c_str());
315 : }
316 : }
317 5 : CPLHTTPDestroyResult(psResult);
318 : }
319 5 : osAccessToken = oResponse.FetchNameValueDef("access_token", "");
320 : const GIntBig nExpiresOn =
321 5 : CPLAtoGIntBig(oResponse.FetchNameValueDef("expires_on", ""));
322 5 : if (!osAccessToken.empty() && nExpiresOn > 0)
323 : {
324 10 : CPLAzureCachedToken cachedToken;
325 5 : cachedToken.osAccessToken = osAccessToken;
326 5 : cachedToken.nExpiresOn = nExpiresOn;
327 5 : goMapIMDSURLToCachedToken[osURLResource] = std::move(cachedToken);
328 5 : CPLDebug("AZURE", "Storing credentials for %s until " CPL_FRMT_GIB,
329 : osURLResource.c_str(), nExpiresOn);
330 : }
331 :
332 5 : return !osAccessToken.empty();
333 : }
334 :
335 : /************************************************************************/
336 : /* GetConfigurationFromWorkloadIdentity() */
337 : /************************************************************************/
338 :
339 : // Last timestamp AZURE_FEDERATED_TOKEN_FILE was read
340 : static GIntBig gnLastReadFederatedTokenFile = 0;
341 : static std::string gosFederatedToken{};
342 :
343 : // Azure Active Directory Workload Identity, typically for Azure Kubernetes
344 : // Cf https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/identity/azure-identity/azure/identity/_credentials/workload_identity.py
345 23 : static bool GetConfigurationFromWorkloadIdentity(std::string &osAccessToken)
346 : {
347 : const std::string AZURE_CLIENT_ID(
348 46 : CPLGetConfigOption("AZURE_CLIENT_ID", ""));
349 : const std::string AZURE_TENANT_ID(
350 46 : CPLGetConfigOption("AZURE_TENANT_ID", ""));
351 : const std::string AZURE_AUTHORITY_HOST(
352 46 : CPLGetConfigOption("AZURE_AUTHORITY_HOST", ""));
353 : const std::string AZURE_FEDERATED_TOKEN_FILE(
354 46 : CPLGetConfigOption("AZURE_FEDERATED_TOKEN_FILE", ""));
355 39 : if (AZURE_CLIENT_ID.empty() || AZURE_TENANT_ID.empty() ||
356 39 : AZURE_AUTHORITY_HOST.empty() || AZURE_FEDERATED_TOKEN_FILE.empty())
357 : {
358 15 : return false;
359 : }
360 :
361 16 : std::lock_guard<std::mutex> guard(gMutex);
362 :
363 : time_t nCurTime;
364 8 : time(&nCurTime);
365 :
366 : // Look for cached token corresponding to this request URL
367 8 : const std::string osURL(AZURE_AUTHORITY_HOST + AZURE_TENANT_ID +
368 16 : "/oauth2/v2.0/token");
369 8 : auto oIter = goMapIMDSURLToCachedToken.find(osURL);
370 8 : if (oIter != goMapIMDSURLToCachedToken.end())
371 : {
372 6 : const auto &oCachedToken = oIter->second;
373 : // Try to reuse credentials if they are still valid, but
374 : // keep one minute of margin...
375 6 : if (nCurTime < oCachedToken.nExpiresOn - 60)
376 : {
377 4 : osAccessToken = oCachedToken.osAccessToken;
378 4 : return true;
379 : }
380 : }
381 :
382 : // Ingest content of AZURE_FEDERATED_TOKEN_FILE if last time was more than
383 : // 600 seconds.
384 4 : if (nCurTime - gnLastReadFederatedTokenFile > 600)
385 : {
386 : auto fp = VSIVirtualHandleUniquePtr(
387 2 : VSIFOpenL(AZURE_FEDERATED_TOKEN_FILE.c_str(), "rb"));
388 2 : if (!fp)
389 : {
390 0 : CPLDebug("AZURE", "Cannot open AZURE_FEDERATED_TOKEN_FILE = %s",
391 : AZURE_FEDERATED_TOKEN_FILE.c_str());
392 0 : return false;
393 : }
394 2 : fp->Seek(0, SEEK_END);
395 2 : const auto nSize = fp->Tell();
396 2 : if (nSize == 0 || nSize > 100 * 1024)
397 : {
398 0 : CPLDebug(
399 : "AZURE",
400 : "Invalid size for AZURE_FEDERATED_TOKEN_FILE = " CPL_FRMT_GUIB,
401 : static_cast<GUIntBig>(nSize));
402 0 : return false;
403 : }
404 2 : fp->Seek(0, SEEK_SET);
405 2 : gosFederatedToken.resize(static_cast<size_t>(nSize));
406 2 : if (fp->Read(&gosFederatedToken[0], gosFederatedToken.size(), 1) != 1)
407 : {
408 0 : CPLDebug("AZURE", "Cannot read AZURE_FEDERATED_TOKEN_FILE");
409 0 : return false;
410 : }
411 2 : gnLastReadFederatedTokenFile = nCurTime;
412 : }
413 :
414 : /* -------------------------------------------------------------------- */
415 : /* Prepare POST request. */
416 : /* -------------------------------------------------------------------- */
417 8 : CPLStringList aosOptions;
418 :
419 : aosOptions.AddString(
420 4 : "HEADERS=Content-Type: application/x-www-form-urlencoded");
421 :
422 8 : std::string osItem("POSTFIELDS=client_assertion=");
423 4 : osItem += CPLAWSURLEncode(gosFederatedToken);
424 : osItem += "&client_assertion_type=urn:ietf:params:oauth:client-assertion-"
425 4 : "type:jwt-bearer";
426 4 : osItem += "&client_id=";
427 4 : osItem += CPLAWSURLEncode(AZURE_CLIENT_ID);
428 4 : osItem += "&grant_type=client_credentials";
429 4 : osItem += "&scope=https://storage.azure.com/.default";
430 4 : aosOptions.AddString(osItem.c_str());
431 :
432 : /* -------------------------------------------------------------------- */
433 : /* Submit request by HTTP. */
434 : /* -------------------------------------------------------------------- */
435 4 : CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
436 4 : if (!psResult)
437 0 : return false;
438 :
439 4 : if (!psResult->pabyData || psResult->pszErrBuf)
440 : {
441 0 : if (psResult->pszErrBuf)
442 0 : CPLDebug("AZURE", "%s", psResult->pszErrBuf);
443 0 : if (psResult->pabyData)
444 0 : CPLDebug("AZURE", "%s", psResult->pabyData);
445 :
446 0 : CPLDebug("AZURE",
447 : "Fetching OAuth2 access code from workload identity failed.");
448 0 : CPLHTTPDestroyResult(psResult);
449 0 : return false;
450 : }
451 :
452 : CPLStringList oResponse =
453 4 : CPLParseKeyValueJson(reinterpret_cast<char *>(psResult->pabyData));
454 4 : CPLHTTPDestroyResult(psResult);
455 :
456 4 : osAccessToken = oResponse.FetchNameValueDef("access_token", "");
457 4 : const int nExpiresIn = atoi(oResponse.FetchNameValueDef("expires_in", ""));
458 4 : if (!osAccessToken.empty() && nExpiresIn > 0)
459 : {
460 8 : CPLAzureCachedToken cachedToken;
461 4 : cachedToken.osAccessToken = osAccessToken;
462 4 : cachedToken.nExpiresOn = nCurTime + nExpiresIn;
463 4 : goMapIMDSURLToCachedToken[osURL] = cachedToken;
464 4 : CPLDebug("AZURE", "Storing credentials for %s until " CPL_FRMT_GIB,
465 : osURL.c_str(), cachedToken.nExpiresOn);
466 : }
467 :
468 4 : return !osAccessToken.empty();
469 : }
470 :
471 : /************************************************************************/
472 : /* GetConfigurationFromManagedIdentities() */
473 : /************************************************************************/
474 :
475 : static bool
476 23 : GetConfigurationFromManagedIdentities(const std::string &osPathForOption,
477 : std::string &osAccessToken)
478 : {
479 23 : if (GetConfigurationFromWorkloadIdentity(osAccessToken))
480 8 : return true;
481 15 : return GetConfigurationFromIMDSCredentials(osPathForOption, osAccessToken);
482 : }
483 :
484 : /************************************************************************/
485 : /* ClearCache() */
486 : /************************************************************************/
487 :
488 662 : void VSIAzureBlobHandleHelper::ClearCache()
489 : {
490 1324 : std::lock_guard<std::mutex> guard(gMutex);
491 662 : goMapIMDSURLToCachedToken.clear();
492 662 : gnLastReadFederatedTokenFile = 0;
493 662 : gosFederatedToken.clear();
494 662 : }
495 :
496 : /************************************************************************/
497 : /* ParseStorageConnectionString() */
498 : /************************************************************************/
499 :
500 : static bool
501 266 : ParseStorageConnectionString(const std::string &osStorageConnectionString,
502 : const std::string &osServicePrefix,
503 : bool &bUseHTTPS, std::string &osEndpoint,
504 : std::string &osStorageAccount,
505 : std::string &osStorageKey, std::string &osSAS)
506 : {
507 : osStorageAccount =
508 266 : AzureCSGetParameter(osStorageConnectionString, "AccountName", false);
509 : osStorageKey =
510 266 : AzureCSGetParameter(osStorageConnectionString, "AccountKey", false);
511 :
512 : const std::string osProtocol(AzureCSGetParameter(
513 532 : osStorageConnectionString, "DefaultEndpointsProtocol", false));
514 266 : bUseHTTPS = (osProtocol != "http");
515 :
516 266 : if (osStorageAccount.empty() || osStorageKey.empty())
517 : {
518 3 : osStorageAccount.clear();
519 3 : osStorageKey.clear();
520 :
521 3 : std::string osBlobEndpoint = RemoveTrailingSlash(AzureCSGetParameter(
522 6 : osStorageConnectionString, "BlobEndpoint", false));
523 6 : osSAS = AzureCSGetParameter(osStorageConnectionString,
524 3 : "SharedAccessSignature", false);
525 3 : if (!osBlobEndpoint.empty() && !osSAS.empty())
526 : {
527 2 : osEndpoint = std::move(osBlobEndpoint);
528 2 : return true;
529 : }
530 :
531 1 : return false;
532 : }
533 :
534 : const std::string osBlobEndpoint =
535 263 : AzureCSGetParameter(osStorageConnectionString, "BlobEndpoint", false);
536 263 : if (!osBlobEndpoint.empty())
537 : {
538 263 : osEndpoint = RemoveTrailingSlash(osBlobEndpoint);
539 : }
540 : else
541 : {
542 : const std::string osEndpointSuffix(AzureCSGetParameter(
543 0 : osStorageConnectionString, "EndpointSuffix", false));
544 0 : if (!osEndpointSuffix.empty())
545 0 : osEndpoint = (bUseHTTPS ? "https://" : "http://") +
546 0 : osStorageAccount + "." + osServicePrefix + "." +
547 0 : RemoveTrailingSlash(osEndpointSuffix);
548 : }
549 :
550 263 : return true;
551 : }
552 :
553 : /************************************************************************/
554 : /* GetConfigurationFromCLIConfigFile() */
555 : /************************************************************************/
556 :
557 9 : static bool GetConfigurationFromCLIConfigFile(
558 : const std::string &osPathForOption, const std::string &osServicePrefix,
559 : bool &bUseHTTPS, std::string &osEndpoint, std::string &osStorageAccount,
560 : std::string &osStorageKey, std::string &osSAS, std::string &osAccessToken,
561 : bool &bFromManagedIdentities)
562 : {
563 : #ifdef _WIN32
564 : const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
565 : constexpr char SEP_STRING[] = "\\";
566 : #else
567 9 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
568 9 : constexpr char SEP_STRING[] = "/";
569 : #endif
570 :
571 18 : std::string osDotAzure(pszHome ? pszHome : "");
572 9 : osDotAzure += SEP_STRING;
573 9 : osDotAzure += ".azure";
574 :
575 : const char *pszAzureConfigDir =
576 9 : CPLGetConfigOption("AZURE_CONFIG_DIR", osDotAzure.c_str());
577 9 : if (pszAzureConfigDir[0] == '\0')
578 5 : return false;
579 :
580 8 : std::string osConfigFilename = pszAzureConfigDir;
581 4 : osConfigFilename += SEP_STRING;
582 4 : osConfigFilename += "config";
583 :
584 4 : VSILFILE *fp = VSIFOpenL(osConfigFilename.c_str(), "rb");
585 8 : std::string osStorageConnectionString;
586 4 : if (fp == nullptr)
587 0 : return false;
588 :
589 4 : bool bInStorageSection = false;
590 25 : while (const char *pszLine = CPLReadLineL(fp))
591 : {
592 21 : if (pszLine[0] == '#' || pszLine[0] == ';')
593 : {
594 : // comment line
595 : }
596 21 : else if (strcmp(pszLine, "[storage]") == 0)
597 : {
598 4 : bInStorageSection = true;
599 : }
600 17 : else if (pszLine[0] == '[')
601 : {
602 4 : bInStorageSection = false;
603 : }
604 13 : else if (bInStorageSection)
605 : {
606 5 : char *pszKey = nullptr;
607 5 : const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
608 5 : if (pszKey && pszValue)
609 : {
610 5 : if (EQUAL(pszKey, "account"))
611 : {
612 2 : osStorageAccount = pszValue;
613 : }
614 3 : else if (EQUAL(pszKey, "connection_string"))
615 : {
616 1 : osStorageConnectionString = pszValue;
617 : }
618 2 : else if (EQUAL(pszKey, "key"))
619 : {
620 1 : osStorageKey = pszValue;
621 : }
622 1 : else if (EQUAL(pszKey, "sas_token"))
623 : {
624 1 : osSAS = pszValue;
625 : // Az CLI apparently uses configparser with
626 : // BasicInterpolation where the % character has a special
627 : // meaning See
628 : // https://docs.python.org/3/library/configparser.html#configparser.BasicInterpolation
629 : // A token might end with %%3D which must be transformed to
630 : // %3D
631 1 : osSAS = CPLString(osSAS).replaceAll("%%", '%');
632 : }
633 : }
634 5 : CPLFree(pszKey);
635 : }
636 21 : }
637 4 : VSIFCloseL(fp);
638 :
639 4 : if (!osStorageConnectionString.empty())
640 : {
641 1 : return ParseStorageConnectionString(
642 : osStorageConnectionString, osServicePrefix, bUseHTTPS, osEndpoint,
643 1 : osStorageAccount, osStorageKey, osSAS);
644 : }
645 :
646 3 : if (osStorageAccount.empty())
647 : {
648 1 : CPLDebug("AZURE", "Missing storage.account in %s",
649 : osConfigFilename.c_str());
650 1 : return false;
651 : }
652 :
653 2 : if (osEndpoint.empty())
654 0 : osEndpoint = (bUseHTTPS ? "https://" : "http://") + osStorageAccount +
655 0 : "." + osServicePrefix + ".core.windows.net";
656 :
657 2 : osAccessToken = CPLGetConfigOption("AZURE_STORAGE_ACCESS_TOKEN", "");
658 2 : if (!osAccessToken.empty())
659 0 : return true;
660 :
661 2 : if (osStorageKey.empty() && osSAS.empty())
662 : {
663 0 : if (CPLTestBool(CPLGetConfigOption("AZURE_NO_SIGN_REQUEST", "NO")))
664 : {
665 0 : return true;
666 : }
667 :
668 0 : std::string osTmpAccessToken;
669 0 : if (GetConfigurationFromManagedIdentities(osPathForOption,
670 : osTmpAccessToken))
671 : {
672 0 : bFromManagedIdentities = true;
673 0 : return true;
674 : }
675 :
676 0 : CPLDebug("AZURE", "Missing storage.key or storage.sas_token in %s",
677 : osConfigFilename.c_str());
678 0 : return false;
679 : }
680 :
681 2 : return true;
682 : }
683 :
684 : /************************************************************************/
685 : /* GetConfiguration() */
686 : /************************************************************************/
687 :
688 306 : bool VSIAzureBlobHandleHelper::GetConfiguration(
689 : const std::string &osPathForOption, CSLConstList papszOptions,
690 : Service eService, bool &bUseHTTPS, std::string &osEndpoint,
691 : std::string &osStorageAccount, std::string &osStorageKey,
692 : std::string &osSAS, std::string &osAccessToken,
693 : bool &bFromManagedIdentities)
694 : {
695 306 : bFromManagedIdentities = false;
696 :
697 : const std::string osServicePrefix(
698 612 : eService == Service::SERVICE_BLOB ? "blob" : "dfs");
699 306 : bUseHTTPS = CPLTestBool(VSIGetPathSpecificOption(
700 : osPathForOption.c_str(), "CPL_AZURE_USE_HTTPS", "YES"));
701 612 : osEndpoint = RemoveTrailingSlash(VSIGetPathSpecificOption(
702 306 : osPathForOption.c_str(), "CPL_AZURE_ENDPOINT", ""));
703 :
704 : const std::string osStorageConnectionString(CSLFetchNameValueDef(
705 : papszOptions, "AZURE_STORAGE_CONNECTION_STRING",
706 : VSIGetPathSpecificOption(osPathForOption.c_str(),
707 612 : "AZURE_STORAGE_CONNECTION_STRING", "")));
708 306 : if (!osStorageConnectionString.empty())
709 : {
710 265 : return ParseStorageConnectionString(
711 : osStorageConnectionString, osServicePrefix, bUseHTTPS, osEndpoint,
712 265 : osStorageAccount, osStorageKey, osSAS);
713 : }
714 : else
715 : {
716 : osStorageAccount = CSLFetchNameValueDef(
717 : papszOptions, "AZURE_STORAGE_ACCOUNT",
718 : VSIGetPathSpecificOption(osPathForOption.c_str(),
719 41 : "AZURE_STORAGE_ACCOUNT", ""));
720 41 : if (!osStorageAccount.empty())
721 : {
722 32 : if (osEndpoint.empty())
723 34 : osEndpoint = (bUseHTTPS ? "https://" : "http://") +
724 34 : osStorageAccount + "." + osServicePrefix +
725 17 : ".core.windows.net";
726 :
727 : osAccessToken = CSLFetchNameValueDef(
728 : papszOptions, "AZURE_STORAGE_ACCESS_TOKEN",
729 : VSIGetPathSpecificOption(osPathForOption.c_str(),
730 32 : "AZURE_STORAGE_ACCESS_TOKEN", ""));
731 32 : if (!osAccessToken.empty())
732 1 : return true;
733 :
734 : osStorageKey = CSLFetchNameValueDef(
735 : papszOptions, "AZURE_STORAGE_ACCESS_KEY",
736 : VSIGetPathSpecificOption(osPathForOption.c_str(),
737 31 : "AZURE_STORAGE_ACCESS_KEY", ""));
738 31 : if (osStorageKey.empty())
739 : {
740 : osSAS = VSIGetPathSpecificOption(
741 : osPathForOption.c_str(), "AZURE_STORAGE_SAS_TOKEN",
742 : CPLGetConfigOption("AZURE_SAS",
743 27 : "")); // AZURE_SAS for GDAL < 3.5
744 27 : if (osSAS.empty())
745 : {
746 19 : if (CPLTestBool(VSIGetPathSpecificOption(
747 : osPathForOption.c_str(), "AZURE_NO_SIGN_REQUEST",
748 : "NO")))
749 : {
750 7 : return true;
751 : }
752 :
753 24 : std::string osTmpAccessToken;
754 12 : if (GetConfigurationFromManagedIdentities(osPathForOption,
755 : osTmpAccessToken))
756 : {
757 11 : bFromManagedIdentities = true;
758 11 : return true;
759 : }
760 :
761 1 : const char *pszMsg =
762 : "AZURE_STORAGE_ACCESS_KEY or AZURE_STORAGE_SAS_TOKEN "
763 : "or AZURE_NO_SIGN_REQUEST configuration option "
764 : "not defined";
765 1 : CPLDebug("AZURE", "%s", pszMsg);
766 1 : VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
767 1 : return false;
768 : }
769 : }
770 12 : return true;
771 : }
772 : }
773 :
774 9 : if (GetConfigurationFromCLIConfigFile(
775 : osPathForOption, osServicePrefix, bUseHTTPS, osEndpoint,
776 : osStorageAccount, osStorageKey, osSAS, osAccessToken,
777 : bFromManagedIdentities))
778 : {
779 3 : return true;
780 : }
781 :
782 6 : const char *pszMsg =
783 : "No valid Azure credentials found. "
784 : "For authenticated requests, you need to set "
785 : "AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY, "
786 : "AZURE_STORAGE_SAS_TOKEN, "
787 : "AZURE_STORAGE_CONNECTION_STRING, or other configuration "
788 : "options. Consult "
789 : "https://gdal.org/en/stable/user/"
790 : "virtual_file_systems.html#vsiaz-microsoft-azure-blob-files "
791 : "for more details. "
792 : "For unauthenticated requests on public resources, set the "
793 : "AZURE_NO_SIGN_REQUEST configuration option to YES.";
794 6 : CPLDebug("AZURE", "%s", pszMsg);
795 6 : VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
796 6 : return false;
797 : }
798 :
799 : /************************************************************************/
800 : /* BuildFromURI() */
801 : /************************************************************************/
802 :
803 306 : VSIAzureBlobHandleHelper *VSIAzureBlobHandleHelper::BuildFromURI(
804 : const char *pszURI, const char *pszFSPrefix,
805 : const char *pszURIForPathSpecificOption, CSLConstList papszOptions)
806 : {
807 306 : if (strcmp(pszFSPrefix, "/vsiaz/") != 0 &&
808 97 : strcmp(pszFSPrefix, "/vsiaz_streaming/") != 0 &&
809 91 : strcmp(pszFSPrefix, "/vsiadls/") != 0)
810 : {
811 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unsupported FS prefix");
812 0 : return nullptr;
813 : }
814 :
815 612 : const auto eService = strcmp(pszFSPrefix, "/vsiaz/") == 0 ||
816 97 : strcmp(pszFSPrefix, "/vsiaz_streaming/") == 0
817 306 : ? Service::SERVICE_BLOB
818 : : Service::SERVICE_ADLS;
819 :
820 : std::string osPathForOption(
821 612 : eService == Service::SERVICE_BLOB ? "/vsiaz/" : "/vsiadls/");
822 : osPathForOption +=
823 306 : pszURIForPathSpecificOption ? pszURIForPathSpecificOption : pszURI;
824 :
825 306 : bool bUseHTTPS = true;
826 612 : std::string osStorageAccount;
827 612 : std::string osStorageKey;
828 612 : std::string osEndpoint;
829 612 : std::string osSAS;
830 612 : std::string osAccessToken;
831 306 : bool bFromManagedIdentities = false;
832 :
833 306 : if (!GetConfiguration(osPathForOption, papszOptions, eService, bUseHTTPS,
834 : osEndpoint, osStorageAccount, osStorageKey, osSAS,
835 : osAccessToken, bFromManagedIdentities))
836 : {
837 8 : return nullptr;
838 : }
839 :
840 298 : if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
841 : "AZURE_NO_SIGN_REQUEST", "NO")))
842 : {
843 8 : osStorageKey.clear();
844 8 : osSAS.clear();
845 8 : osAccessToken.clear();
846 : }
847 :
848 : // pszURI == bucket/object
849 596 : const std::string osBucketObject(pszURI);
850 596 : std::string osBucket(osBucketObject);
851 298 : std::string osObjectKey;
852 298 : size_t nSlashPos = osBucketObject.find('/');
853 298 : if (nSlashPos != std::string::npos)
854 : {
855 213 : osBucket = osBucketObject.substr(0, nSlashPos);
856 213 : osObjectKey = osBucketObject.substr(nSlashPos + 1);
857 : }
858 :
859 : return new VSIAzureBlobHandleHelper(
860 : osPathForOption, osEndpoint, osBucket, osObjectKey, osStorageAccount,
861 298 : osStorageKey, osSAS, osAccessToken, bFromManagedIdentities);
862 : }
863 :
864 : /************************************************************************/
865 : /* BuildURL() */
866 : /************************************************************************/
867 :
868 934 : std::string VSIAzureBlobHandleHelper::BuildURL(const std::string &osEndpoint,
869 : const std::string &osBucket,
870 : const std::string &osObjectKey,
871 : const std::string &osSAS)
872 : {
873 934 : std::string osURL = osEndpoint;
874 934 : osURL += "/";
875 934 : osURL += CPLAWSURLEncode(osBucket, false);
876 934 : if (!osObjectKey.empty())
877 453 : osURL += "/" + CPLAWSURLEncode(osObjectKey, false);
878 934 : if (!osSAS.empty())
879 11 : osURL += '?' + osSAS;
880 934 : return osURL;
881 : }
882 :
883 : /************************************************************************/
884 : /* RebuildURL() */
885 : /************************************************************************/
886 :
887 636 : void VSIAzureBlobHandleHelper::RebuildURL()
888 : {
889 636 : m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, std::string());
890 636 : m_osURL += GetQueryString(false);
891 636 : if (!m_osSAS.empty())
892 11 : m_osURL += (m_oMapQueryParameters.empty() ? '?' : '&') + m_osSAS;
893 636 : }
894 :
895 : /************************************************************************/
896 : /* GetSASQueryString() */
897 : /************************************************************************/
898 :
899 72 : std::string VSIAzureBlobHandleHelper::GetSASQueryString() const
900 : {
901 72 : if (!m_osSAS.empty())
902 4 : return '?' + m_osSAS;
903 68 : return std::string();
904 : }
905 :
906 : /************************************************************************/
907 : /* GetCurlHeaders() */
908 : /************************************************************************/
909 :
910 : struct curl_slist *
911 235 : VSIAzureBlobHandleHelper::GetCurlHeaders(const std::string &osVerb,
912 : struct curl_slist *psHeaders,
913 : const void *, size_t) const
914 : {
915 235 : if (m_bFromManagedIdentities || !m_osAccessToken.empty())
916 : {
917 24 : std::string osAccessToken;
918 12 : if (m_bFromManagedIdentities)
919 : {
920 11 : if (!GetConfigurationFromManagedIdentities(m_osPathForOption,
921 : osAccessToken))
922 0 : return nullptr;
923 : }
924 : else
925 : {
926 1 : osAccessToken = m_osAccessToken;
927 : }
928 :
929 : // Do not use CPLSPrintf() as we could get over the 8K character limit
930 : // with very large SAS tokens
931 12 : std::string osAuthorization = "Authorization: Bearer ";
932 12 : osAuthorization += osAccessToken;
933 12 : psHeaders = curl_slist_append(psHeaders, osAuthorization.c_str());
934 12 : psHeaders = curl_slist_append(psHeaders, "x-ms-version: 2019-12-12");
935 12 : return psHeaders;
936 : }
937 :
938 446 : std::string osResource;
939 223 : const auto nSlashSlashPos = m_osEndpoint.find("//");
940 223 : if (nSlashSlashPos != std::string::npos)
941 : {
942 223 : const auto nResourcePos = m_osEndpoint.find('/', nSlashSlashPos + 2);
943 223 : if (nResourcePos != std::string::npos)
944 215 : osResource = m_osEndpoint.substr(nResourcePos);
945 : }
946 223 : osResource += "/" + m_osBucket;
947 223 : if (!m_osObjectKey.empty())
948 141 : osResource += "/" + CPLAWSURLEncode(m_osObjectKey, false);
949 :
950 446 : return GetAzureBlobHeaders(osVerb, psHeaders, osResource,
951 223 : m_oMapQueryParameters, m_osStorageAccount,
952 223 : m_osStorageKey, m_bIncludeMSVersion);
953 : }
954 :
955 : /************************************************************************/
956 : /* CanRestartOnError() */
957 : /************************************************************************/
958 :
959 18 : bool VSIAzureBlobHandleHelper::CanRestartOnError(const char *pszErrorMsg,
960 : const char *pszHeaders,
961 : bool bSetError)
962 : {
963 18 : if (pszErrorMsg[0] == '\xEF' && pszErrorMsg[1] == '\xBB' &&
964 2 : pszErrorMsg[2] == '\xBF')
965 2 : pszErrorMsg += 3;
966 :
967 : #ifdef DEBUG_VERBOSE
968 : CPLDebug("AZURE", "%s", pszErrorMsg);
969 : CPLDebug("AZURE", "%s", pszHeaders ? pszHeaders : "");
970 : #endif
971 :
972 18 : if (STARTS_WITH(pszErrorMsg, "HTTP/") && pszHeaders &&
973 16 : STARTS_WITH(pszHeaders, "HTTP/"))
974 : {
975 16 : if (bSetError)
976 : {
977 32 : std::string osMessage;
978 32 : std::string osTmpMessage(pszHeaders);
979 16 : auto nPos = osTmpMessage.find(' ');
980 16 : if (nPos != std::string::npos)
981 : {
982 16 : nPos = osTmpMessage.find(' ', nPos + 1);
983 16 : if (nPos != std::string::npos)
984 : {
985 16 : auto nPos2 = osTmpMessage.find('\r', nPos + 1);
986 16 : if (nPos2 != std::string::npos)
987 : osMessage =
988 16 : osTmpMessage.substr(nPos + 1, nPos2 - nPos - 1);
989 : }
990 : }
991 16 : if (strstr(pszHeaders, "x-ms-error-code: BlobNotFound") || // vsiaz
992 16 : strstr(pszHeaders, "x-ms-error-code: PathNotFound") // vsiadls
993 : )
994 : {
995 0 : VSIError(VSIE_ObjectNotFound, "%s", osMessage.c_str());
996 : }
997 16 : else if (strstr(pszHeaders,
998 16 : "x-ms-error-code: InvalidAuthenticationInfo") ||
999 16 : strstr(pszHeaders,
1000 : "x-ms-error-code: AuthenticationFailed"))
1001 : {
1002 1 : VSIError(VSIE_InvalidCredentials, "%s", osMessage.c_str());
1003 : }
1004 : // /vsiadls
1005 15 : else if (strstr(pszHeaders, "x-ms-error-code: FilesystemNotFound"))
1006 : {
1007 0 : VSIError(VSIE_BucketNotFound, "%s", osMessage.c_str());
1008 : }
1009 : else
1010 : {
1011 15 : CPLDebug("AZURE", "%s", pszHeaders);
1012 : }
1013 : }
1014 16 : return false;
1015 : }
1016 :
1017 2 : if (!STARTS_WITH(pszErrorMsg, "<?xml") &&
1018 0 : !STARTS_WITH(pszErrorMsg, "<Error>"))
1019 : {
1020 0 : if (bSetError)
1021 : {
1022 0 : VSIError(VSIE_ObjectStorageGenericError,
1023 : "Invalid Azure response: %s", pszErrorMsg);
1024 : }
1025 0 : return false;
1026 : }
1027 :
1028 4 : auto psTree = CPLXMLTreeCloser(CPLParseXMLString(pszErrorMsg));
1029 2 : if (psTree == nullptr)
1030 : {
1031 0 : if (bSetError)
1032 : {
1033 0 : VSIError(VSIE_ObjectStorageGenericError,
1034 : "Malformed Azure XML response: %s", pszErrorMsg);
1035 : }
1036 0 : return false;
1037 : }
1038 :
1039 2 : const char *pszCode = CPLGetXMLValue(psTree.get(), "=Error.Code", nullptr);
1040 2 : if (pszCode == nullptr)
1041 : {
1042 0 : if (bSetError)
1043 : {
1044 0 : VSIError(VSIE_ObjectStorageGenericError,
1045 : "Malformed Azure XML response: %s", pszErrorMsg);
1046 : }
1047 0 : return false;
1048 : }
1049 :
1050 2 : if (bSetError)
1051 : {
1052 : // Translate AWS errors into VSI errors.
1053 : const char *pszMessage =
1054 2 : CPLGetXMLValue(psTree.get(), "=Error.Message", nullptr);
1055 4 : std::string osMessage;
1056 2 : if (pszMessage)
1057 : {
1058 2 : osMessage = pszMessage;
1059 2 : const auto nPos = osMessage.find("\nRequestId:");
1060 2 : if (nPos != std::string::npos)
1061 2 : osMessage.resize(nPos);
1062 : }
1063 :
1064 2 : if (pszMessage == nullptr)
1065 : {
1066 0 : VSIError(VSIE_ObjectStorageGenericError, "%s", pszErrorMsg);
1067 : }
1068 2 : else if (EQUAL(pszCode, "ContainerNotFound"))
1069 : {
1070 0 : VSIError(VSIE_BucketNotFound, "%s", osMessage.c_str());
1071 : }
1072 : else
1073 : {
1074 2 : VSIError(VSIE_ObjectStorageGenericError, "%s: %s", pszCode,
1075 : pszMessage);
1076 : }
1077 : }
1078 :
1079 2 : return false;
1080 : }
1081 :
1082 : /************************************************************************/
1083 : /* GetSignedURL() */
1084 : /************************************************************************/
1085 :
1086 7 : std::string VSIAzureBlobHandleHelper::GetSignedURL(CSLConstList papszOptions)
1087 : {
1088 7 : if (m_osStorageKey.empty())
1089 3 : return m_osURL;
1090 :
1091 8 : std::string osStartDate(CPLGetAWS_SIGN4_Timestamp(time(nullptr)));
1092 4 : const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
1093 4 : if (pszStartDate)
1094 2 : osStartDate = pszStartDate;
1095 4 : int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
1096 4 : if (sscanf(osStartDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear,
1097 4 : &nMonth, &nDay, &nHour, &nMin, &nSec) < 3)
1098 : {
1099 0 : return std::string();
1100 : }
1101 : osStartDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear, nMonth,
1102 4 : nDay, nHour, nMin, nSec);
1103 :
1104 : struct tm brokendowntime;
1105 4 : brokendowntime.tm_year = nYear - 1900;
1106 4 : brokendowntime.tm_mon = nMonth - 1;
1107 4 : brokendowntime.tm_mday = nDay;
1108 4 : brokendowntime.tm_hour = nHour;
1109 4 : brokendowntime.tm_min = nMin;
1110 4 : brokendowntime.tm_sec = nSec;
1111 4 : GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
1112 : GIntBig nEndDate =
1113 : nStartDate +
1114 4 : atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
1115 4 : CPLUnixTimeToYMDHMS(nEndDate, &brokendowntime);
1116 4 : nYear = brokendowntime.tm_year + 1900;
1117 4 : nMonth = brokendowntime.tm_mon + 1;
1118 4 : nDay = brokendowntime.tm_mday;
1119 4 : nHour = brokendowntime.tm_hour;
1120 4 : nMin = brokendowntime.tm_min;
1121 4 : nSec = brokendowntime.tm_sec;
1122 : std::string osEndDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear,
1123 8 : nMonth, nDay, nHour, nMin, nSec);
1124 :
1125 8 : std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
1126 : std::string osSignedPermissions(CSLFetchNameValueDef(
1127 : papszOptions, "SIGNEDPERMISSIONS",
1128 4 : (EQUAL(osVerb.c_str(), "GET") || EQUAL(osVerb.c_str(), "HEAD")) ? "r"
1129 12 : : "w"));
1130 :
1131 : std::string osSignedIdentifier(
1132 8 : CSLFetchNameValueDef(papszOptions, "SIGNEDIDENTIFIER", ""));
1133 :
1134 8 : const std::string osSignedVersion("2020-12-06");
1135 8 : const std::string osSignedProtocol("https");
1136 8 : const std::string osSignedResource("b"); // blob
1137 :
1138 8 : std::string osCanonicalizedResource("/blob/");
1139 4 : osCanonicalizedResource += CPLAWSURLEncode(m_osStorageAccount, false);
1140 4 : osCanonicalizedResource += '/';
1141 4 : osCanonicalizedResource += CPLAWSURLEncode(m_osBucket, false);
1142 4 : osCanonicalizedResource += '/';
1143 4 : osCanonicalizedResource += CPLAWSURLEncode(m_osObjectKey, false);
1144 :
1145 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
1146 8 : std::string osStringToSign;
1147 4 : osStringToSign += osSignedPermissions + "\n";
1148 4 : osStringToSign += osStartDate + "\n";
1149 4 : osStringToSign += osEndDate + "\n";
1150 4 : osStringToSign += osCanonicalizedResource + "\n";
1151 4 : osStringToSign += osSignedIdentifier + "\n";
1152 4 : osStringToSign += "\n"; // signedIP
1153 4 : osStringToSign += osSignedProtocol + "\n";
1154 4 : osStringToSign += osSignedVersion + "\n";
1155 4 : osStringToSign += osSignedResource + "\n";
1156 4 : osStringToSign += "\n"; // signedSnapshotTime
1157 4 : osStringToSign += "\n"; // signedEncryptionScope
1158 4 : osStringToSign += "\n"; // rscc
1159 4 : osStringToSign += "\n"; // rscd
1160 4 : osStringToSign += "\n"; // rsce
1161 4 : osStringToSign += "\n"; // rscl
1162 :
1163 : #ifdef DEBUG_VERBOSE
1164 : CPLDebug("AZURE", "osStringToSign = %s", osStringToSign.c_str());
1165 : #endif
1166 :
1167 : /* -------------------------------------------------------------------- */
1168 : /* Compute signature. */
1169 : /* -------------------------------------------------------------------- */
1170 : std::string osSignature(
1171 8 : CPLAzureGetSignature(osStringToSign, m_osStorageKey));
1172 :
1173 4 : ResetQueryParameters();
1174 4 : AddQueryParameter("sv", osSignedVersion);
1175 4 : AddQueryParameter("st", osStartDate);
1176 4 : AddQueryParameter("se", osEndDate);
1177 4 : AddQueryParameter("sr", osSignedResource);
1178 4 : AddQueryParameter("sp", osSignedPermissions);
1179 4 : AddQueryParameter("spr", osSignedProtocol);
1180 4 : AddQueryParameter("sig", osSignature);
1181 4 : if (!osSignedIdentifier.empty())
1182 0 : AddQueryParameter("si", osSignedIdentifier);
1183 4 : return m_osURL;
1184 : }
1185 :
1186 : #endif // HAVE_CURL
1187 :
1188 : //! @endcond
|