Line data Source code
1 : /**********************************************************************
2 : *
3 : * Name: cpl_alibaba_oss.h
4 : * Project: CPL - Common Portability Library
5 : * Purpose: Alibaba Cloud Object Storage Service
6 : * Author: Even Rouault <even.rouault at spatialys.com>
7 : *
8 : **********************************************************************
9 : * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : //! @cond Doxygen_Suppress
15 :
16 : #include "cpl_alibaba_oss.h"
17 : #include "cpl_vsi_error.h"
18 : #include "cpl_time.h"
19 : #include "cpl_minixml.h"
20 : #include "cpl_multiproc.h"
21 : #include "cpl_http.h"
22 : #include "cpl_sha1.h"
23 : #include <algorithm>
24 :
25 : // #define DEBUG_VERBOSE 1
26 :
27 : #ifdef HAVE_CURL
28 :
29 : /************************************************************************/
30 : /* GetSignature() */
31 : /************************************************************************/
32 :
33 69 : static std::string GetSignature(const std::string &osStringToSign,
34 : const std::string &osSecretAccessKey)
35 : {
36 :
37 : /* -------------------------------------------------------------------- */
38 : /* Compute signature. */
39 : /* -------------------------------------------------------------------- */
40 69 : GByte abySignature[CPL_SHA1_HASH_SIZE] = {};
41 138 : CPL_HMAC_SHA1(osSecretAccessKey.c_str(), osSecretAccessKey.size(),
42 69 : osStringToSign.c_str(), osStringToSign.size(), abySignature);
43 69 : char *pszBase64 = CPLBase64Encode(sizeof(abySignature), abySignature);
44 69 : std::string osSignature(pszBase64);
45 69 : CPLFree(pszBase64);
46 :
47 138 : return osSignature;
48 : }
49 :
50 : /************************************************************************/
51 : /* CPLGetOSSHeaders() */
52 : /************************************************************************/
53 :
54 : // See:
55 : // https://www.alibabacloud.com/help/doc-detail/31951.htm?spm=a3c0i.o31982en.b99.178.5HUTqV
56 : static struct curl_slist *
57 68 : CPLGetOSSHeaders(const std::string &osSecretAccessKey,
58 : const std::string &osAccessKeyId, const std::string &osVerb,
59 : const struct curl_slist *psExistingHeaders,
60 : const std::string &osCanonicalizedResource)
61 : {
62 136 : std::string osDate = CPLGetConfigOption("CPL_OSS_TIMESTAMP", "");
63 68 : if (osDate.empty())
64 : {
65 0 : osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
66 : }
67 :
68 136 : std::map<std::string, std::string> oSortedMapHeaders;
69 : std::string osCanonicalizedHeaders(
70 : IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
71 136 : oSortedMapHeaders, psExistingHeaders, "x-oss-"));
72 :
73 136 : std::string osStringToSign;
74 68 : osStringToSign += osVerb + "\n";
75 : osStringToSign +=
76 68 : CPLAWSGetHeaderVal(psExistingHeaders, "Content-MD5") + "\n";
77 : osStringToSign +=
78 68 : CPLAWSGetHeaderVal(psExistingHeaders, "Content-Type") + "\n";
79 68 : osStringToSign += osDate + "\n";
80 68 : osStringToSign += osCanonicalizedHeaders;
81 68 : osStringToSign += osCanonicalizedResource;
82 : #ifdef DEBUG_VERBOSE
83 : CPLDebug("OSS", "osStringToSign = %s", osStringToSign.c_str());
84 : #endif
85 :
86 : /* -------------------------------------------------------------------- */
87 : /* Build authorization header. */
88 : /* -------------------------------------------------------------------- */
89 :
90 68 : std::string osAuthorization("OSS ");
91 68 : osAuthorization += osAccessKeyId;
92 68 : osAuthorization += ":";
93 68 : osAuthorization += GetSignature(osStringToSign, osSecretAccessKey);
94 :
95 : #ifdef DEBUG_VERBOSE
96 : CPLDebug("OSS", "osAuthorization='%s'", osAuthorization.c_str());
97 : #endif
98 :
99 68 : struct curl_slist *headers = nullptr;
100 : headers =
101 68 : curl_slist_append(headers, CPLSPrintf("Date: %s", osDate.c_str()));
102 68 : headers = curl_slist_append(
103 : headers, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
104 136 : return headers;
105 : }
106 :
107 : /************************************************************************/
108 : /* VSIOSSHandleHelper() */
109 : /************************************************************************/
110 87 : VSIOSSHandleHelper::VSIOSSHandleHelper(const std::string &osSecretAccessKey,
111 : const std::string &osAccessKeyId,
112 : const std::string &osEndpoint,
113 : const std::string &osBucket,
114 : const std::string &osObjectKey,
115 87 : bool bUseHTTPS, bool bUseVirtualHosting)
116 : : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, bUseHTTPS,
117 : bUseVirtualHosting)),
118 : m_osSecretAccessKey(osSecretAccessKey), m_osAccessKeyId(osAccessKeyId),
119 : m_osEndpoint(osEndpoint), m_osBucket(osBucket),
120 : m_osObjectKey(osObjectKey), m_bUseHTTPS(bUseHTTPS),
121 87 : m_bUseVirtualHosting(bUseVirtualHosting)
122 : {
123 87 : VSIOSSUpdateParams::UpdateHandleFromMap(this);
124 87 : }
125 :
126 : /************************************************************************/
127 : /* ~VSIOSSHandleHelper() */
128 : /************************************************************************/
129 :
130 174 : VSIOSSHandleHelper::~VSIOSSHandleHelper()
131 : {
132 1914 : for (size_t i = 0; i < m_osSecretAccessKey.size(); i++)
133 1827 : m_osSecretAccessKey[i] = 0;
134 174 : }
135 :
136 : /************************************************************************/
137 : /* BuildURL() */
138 : /************************************************************************/
139 :
140 145 : std::string VSIOSSHandleHelper::BuildURL(const std::string &osEndpoint,
141 : const std::string &osBucket,
142 : const std::string &osObjectKey,
143 : bool bUseHTTPS,
144 : bool bUseVirtualHosting)
145 : {
146 145 : const char *pszProtocol = (bUseHTTPS) ? "https" : "http";
147 145 : if (osBucket.empty())
148 : {
149 8 : return CPLSPrintf("%s://%s", pszProtocol, osEndpoint.c_str());
150 : }
151 137 : else if (bUseVirtualHosting)
152 : return CPLSPrintf("%s://%s.%s/%s", pszProtocol, osBucket.c_str(),
153 : osEndpoint.c_str(),
154 0 : CPLAWSURLEncode(osObjectKey, false).c_str());
155 : else
156 : return CPLSPrintf("%s://%s/%s/%s", pszProtocol, osEndpoint.c_str(),
157 : osBucket.c_str(),
158 274 : CPLAWSURLEncode(osObjectKey, false).c_str());
159 : }
160 :
161 : /************************************************************************/
162 : /* RebuildURL() */
163 : /************************************************************************/
164 :
165 58 : void VSIOSSHandleHelper::RebuildURL()
166 : {
167 58 : m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, m_bUseHTTPS,
168 58 : m_bUseVirtualHosting);
169 58 : m_osURL += GetQueryString(false);
170 58 : }
171 :
172 : /************************************************************************/
173 : /* GetConfiguration() */
174 : /************************************************************************/
175 :
176 119 : bool VSIOSSHandleHelper::GetConfiguration(const std::string &osPathForOption,
177 : CSLConstList papszOptions,
178 : std::string &osSecretAccessKey,
179 : std::string &osAccessKeyId)
180 : {
181 : osSecretAccessKey = CSLFetchNameValueDef(
182 : papszOptions, "OSS_SECRET_ACCESS_KEY",
183 : VSIGetPathSpecificOption(osPathForOption.c_str(),
184 119 : "OSS_SECRET_ACCESS_KEY", ""));
185 :
186 119 : if (!osSecretAccessKey.empty())
187 : {
188 : osAccessKeyId = CSLFetchNameValueDef(
189 : papszOptions, "OSS_ACCESS_KEY_ID",
190 : VSIGetPathSpecificOption(osPathForOption.c_str(),
191 89 : "OSS_ACCESS_KEY_ID", ""));
192 89 : if (osAccessKeyId.empty())
193 : {
194 1 : VSIError(VSIE_InvalidCredentials,
195 : "OSS_ACCESS_KEY_ID configuration option not defined");
196 1 : return false;
197 : }
198 :
199 88 : return true;
200 : }
201 :
202 30 : VSIError(VSIE_InvalidCredentials,
203 : "OSS_SECRET_ACCESS_KEY configuration option not defined");
204 30 : return false;
205 : }
206 :
207 : /************************************************************************/
208 : /* BuildFromURI() */
209 : /************************************************************************/
210 :
211 119 : VSIOSSHandleHelper *VSIOSSHandleHelper::BuildFromURI(const char *pszURI,
212 : const char *pszFSPrefix,
213 : bool bAllowNoObject,
214 : CSLConstList papszOptions)
215 : {
216 238 : std::string osPathForOption("/vsioss/");
217 119 : if (pszURI)
218 119 : osPathForOption += pszURI;
219 :
220 238 : std::string osSecretAccessKey;
221 238 : std::string osAccessKeyId;
222 119 : if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
223 : osAccessKeyId))
224 : {
225 31 : return nullptr;
226 : }
227 :
228 : const std::string osEndpoint = CSLFetchNameValueDef(
229 : papszOptions, "OSS_ENDPOINT",
230 : VSIGetPathSpecificOption(osPathForOption.c_str(), "OSS_ENDPOINT",
231 176 : "oss-us-east-1.aliyuncs.com"));
232 176 : std::string osBucket;
233 176 : std::string osObjectKey;
234 172 : if (pszURI != nullptr && pszURI[0] != '\0' &&
235 84 : !GetBucketAndObjectKey(pszURI, pszFSPrefix, bAllowNoObject, osBucket,
236 : osObjectKey))
237 : {
238 1 : return nullptr;
239 : }
240 87 : const bool bUseHTTPS = CPLTestBool(
241 : VSIGetPathSpecificOption(osPathForOption.c_str(), "OSS_HTTPS", "YES"));
242 : const bool bIsValidNameForVirtualHosting =
243 87 : osBucket.find('.') == std::string::npos;
244 87 : const bool bUseVirtualHosting = CPLTestBool(VSIGetPathSpecificOption(
245 : osPathForOption.c_str(), "OSS_VIRTUAL_HOSTING",
246 : bIsValidNameForVirtualHosting ? "TRUE" : "FALSE"));
247 : return new VSIOSSHandleHelper(osSecretAccessKey, osAccessKeyId, osEndpoint,
248 : osBucket, osObjectKey, bUseHTTPS,
249 87 : bUseVirtualHosting);
250 : }
251 :
252 : /************************************************************************/
253 : /* GetCurlHeaders() */
254 : /************************************************************************/
255 :
256 68 : struct curl_slist *VSIOSSHandleHelper::GetCurlHeaders(
257 : const std::string &osVerb, const struct curl_slist *psExistingHeaders,
258 : const void * /*pabyDataContent*/, size_t /*nBytesContent*/) const
259 : {
260 136 : std::string osCanonicalQueryString;
261 68 : if (!m_osObjectKey.empty())
262 : {
263 58 : osCanonicalQueryString = GetQueryString(false);
264 : }
265 :
266 : std::string osCanonicalizedResource(
267 68 : m_osBucket.empty() ? std::string("/")
268 268 : : "/" + m_osBucket + "/" + m_osObjectKey);
269 68 : osCanonicalizedResource += osCanonicalQueryString;
270 :
271 68 : return CPLGetOSSHeaders(m_osSecretAccessKey, m_osAccessKeyId, osVerb,
272 136 : psExistingHeaders, osCanonicalizedResource);
273 : }
274 :
275 : /************************************************************************/
276 : /* CanRestartOnError() */
277 : /************************************************************************/
278 :
279 10 : bool VSIOSSHandleHelper::CanRestartOnError(const char *pszErrorMsg,
280 : const char *, bool bSetError)
281 : {
282 : #ifdef DEBUG_VERBOSE
283 : CPLDebug("OSS", "%s", pszErrorMsg);
284 : #endif
285 :
286 10 : if (!STARTS_WITH(pszErrorMsg, "<?xml"))
287 : {
288 4 : if (bSetError)
289 : {
290 4 : VSIError(VSIE_ObjectStorageGenericError, "Invalid OSS response: %s",
291 : pszErrorMsg);
292 : }
293 4 : return false;
294 : }
295 :
296 6 : CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
297 6 : if (psTree == nullptr)
298 : {
299 1 : if (bSetError)
300 : {
301 1 : VSIError(VSIE_ObjectStorageGenericError,
302 : "Malformed OSS XML response: %s", pszErrorMsg);
303 : }
304 1 : return false;
305 : }
306 :
307 5 : const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
308 5 : if (pszCode == nullptr)
309 : {
310 1 : CPLDestroyXMLNode(psTree);
311 1 : if (bSetError)
312 : {
313 1 : VSIError(VSIE_ObjectStorageGenericError,
314 : "Malformed OSS XML response: %s", pszErrorMsg);
315 : }
316 1 : return false;
317 : }
318 :
319 4 : if (EQUAL(pszCode, "AccessDenied"))
320 : {
321 : const char *pszEndpoint =
322 1 : CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
323 1 : if (pszEndpoint && pszEndpoint != m_osEndpoint)
324 : {
325 1 : SetEndpoint(pszEndpoint);
326 1 : CPLDebug("OSS", "Switching to endpoint %s", m_osEndpoint.c_str());
327 1 : CPLDestroyXMLNode(psTree);
328 :
329 1 : VSIOSSUpdateParams::UpdateMapFromHandle(this);
330 :
331 1 : return true;
332 : }
333 : }
334 :
335 3 : if (bSetError)
336 : {
337 : // Translate AWS errors into VSI errors.
338 : const char *pszMessage =
339 3 : CPLGetXMLValue(psTree, "=Error.Message", nullptr);
340 :
341 3 : if (pszMessage == nullptr)
342 : {
343 3 : VSIError(VSIE_ObjectStorageGenericError, "%s", pszErrorMsg);
344 : }
345 0 : else if (EQUAL(pszCode, "AccessDenied"))
346 : {
347 0 : VSIError(VSIE_AccessDenied, "%s", pszMessage);
348 : }
349 0 : else if (EQUAL(pszCode, "NoSuchBucket"))
350 : {
351 0 : VSIError(VSIE_BucketNotFound, "%s", pszMessage);
352 : }
353 0 : else if (EQUAL(pszCode, "NoSuchKey"))
354 : {
355 0 : VSIError(VSIE_ObjectNotFound, "%s", pszMessage);
356 : }
357 0 : else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
358 : {
359 0 : VSIError(VSIE_SignatureDoesNotMatch, "%s", pszMessage);
360 : }
361 : else
362 : {
363 0 : VSIError(VSIE_ObjectStorageGenericError, "%s", pszMessage);
364 : }
365 : }
366 :
367 3 : CPLDestroyXMLNode(psTree);
368 :
369 3 : return false;
370 : }
371 :
372 : /************************************************************************/
373 : /* SetEndpoint() */
374 : /************************************************************************/
375 :
376 8 : void VSIOSSHandleHelper::SetEndpoint(const std::string &osStr)
377 : {
378 8 : m_osEndpoint = osStr;
379 8 : RebuildURL();
380 8 : }
381 :
382 : /************************************************************************/
383 : /* GetSignedURL() */
384 : /************************************************************************/
385 :
386 1 : std::string VSIOSSHandleHelper::GetSignedURL(CSLConstList papszOptions)
387 : {
388 1 : GIntBig nStartDate = static_cast<GIntBig>(time(nullptr));
389 1 : const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
390 1 : if (pszStartDate)
391 : {
392 : int nYear, nMonth, nDay, nHour, nMin, nSec;
393 1 : if (sscanf(pszStartDate, "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
394 1 : &nDay, &nHour, &nMin, &nSec) == 6)
395 : {
396 : struct tm brokendowntime;
397 1 : brokendowntime.tm_year = nYear - 1900;
398 1 : brokendowntime.tm_mon = nMonth - 1;
399 1 : brokendowntime.tm_mday = nDay;
400 1 : brokendowntime.tm_hour = nHour;
401 1 : brokendowntime.tm_min = nMin;
402 1 : brokendowntime.tm_sec = nSec;
403 1 : nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
404 : }
405 : }
406 : GIntBig nExpiresIn =
407 : nStartDate +
408 1 : atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
409 : std::string osExpires(CSLFetchNameValueDef(
410 2 : papszOptions, "EXPIRES", CPLSPrintf(CPL_FRMT_GIB, nExpiresIn)));
411 :
412 2 : std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
413 :
414 : std::string osCanonicalizedResource(
415 1 : m_osBucket.empty() ? std::string("/")
416 4 : : "/" + m_osBucket + "/" + m_osObjectKey);
417 :
418 2 : std::string osStringToSign;
419 1 : osStringToSign += osVerb + "\n";
420 1 : osStringToSign += "\n";
421 1 : osStringToSign += "\n";
422 1 : osStringToSign += osExpires + "\n";
423 : // osStringToSign += ; // osCanonicalizedHeaders;
424 1 : osStringToSign += osCanonicalizedResource;
425 : #ifdef DEBUG_VERBOSE
426 : CPLDebug("OSS", "osStringToSign = %s", osStringToSign.c_str());
427 : #endif
428 :
429 2 : std::string osSignature(GetSignature(osStringToSign, m_osSecretAccessKey));
430 :
431 1 : ResetQueryParameters();
432 : // Note:
433 : // https://www.alibabacloud.com/help/doc-detail/31952.htm?spm=a3c0i.o32002en.b99.294.6d70a0fc7cRJfJ
434 : // is wrong on the name of the OSSAccessKeyId parameter !
435 1 : AddQueryParameter("OSSAccessKeyId", m_osAccessKeyId);
436 1 : AddQueryParameter("Expires", osExpires);
437 1 : AddQueryParameter("Signature", osSignature);
438 2 : return m_osURL;
439 : }
440 :
441 : /************************************************************************/
442 : /* UpdateMapFromHandle() */
443 : /************************************************************************/
444 :
445 : std::mutex VSIOSSUpdateParams::gsMutex{};
446 :
447 : std::map<std::string, VSIOSSUpdateParams>
448 : VSIOSSUpdateParams::goMapBucketsToOSSParams{};
449 :
450 1 : void VSIOSSUpdateParams::UpdateMapFromHandle(
451 : VSIOSSHandleHelper *poOSSHandleHelper)
452 : {
453 1 : std::lock_guard<std::mutex> guard(gsMutex);
454 :
455 1 : goMapBucketsToOSSParams[poOSSHandleHelper->GetBucket()] =
456 2 : VSIOSSUpdateParams(poOSSHandleHelper);
457 1 : }
458 :
459 : /************************************************************************/
460 : /* UpdateHandleFromMap() */
461 : /************************************************************************/
462 :
463 87 : void VSIOSSUpdateParams::UpdateHandleFromMap(
464 : VSIOSSHandleHelper *poOSSHandleHelper)
465 : {
466 174 : std::lock_guard<std::mutex> guard(gsMutex);
467 :
468 : std::map<std::string, VSIOSSUpdateParams>::iterator oIter =
469 87 : goMapBucketsToOSSParams.find(poOSSHandleHelper->GetBucket());
470 87 : if (oIter != goMapBucketsToOSSParams.end())
471 : {
472 7 : oIter->second.UpdateHandlerHelper(poOSSHandleHelper);
473 : }
474 87 : }
475 :
476 : /************************************************************************/
477 : /* ClearCache() */
478 : /************************************************************************/
479 :
480 1767 : void VSIOSSUpdateParams::ClearCache()
481 : {
482 3534 : std::lock_guard<std::mutex> guard(gsMutex);
483 :
484 1767 : goMapBucketsToOSSParams.clear();
485 1767 : }
486 :
487 : #endif // HAVE_CURL
488 :
489 : //! @endcond
|