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