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 92 : 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 92 : "OSS_SECRET_ACCESS_KEY", ""));
185 :
186 92 : 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_AWSInvalidCredentials,
195 : "OSS_ACCESS_KEY_ID configuration option not defined");
196 1 : return false;
197 : }
198 :
199 88 : return true;
200 : }
201 :
202 3 : VSIError(VSIE_AWSInvalidCredentials,
203 : "OSS_SECRET_ACCESS_KEY configuration option not defined");
204 3 : return false;
205 : }
206 :
207 : /************************************************************************/
208 : /* BuildFromURI() */
209 : /************************************************************************/
210 :
211 92 : VSIOSSHandleHelper *VSIOSSHandleHelper::BuildFromURI(const char *pszURI,
212 : const char *pszFSPrefix,
213 : bool bAllowNoObject,
214 : CSLConstList papszOptions)
215 : {
216 184 : std::string osPathForOption("/vsioss/");
217 92 : if (pszURI)
218 92 : osPathForOption += pszURI;
219 :
220 184 : std::string osSecretAccessKey;
221 184 : std::string osAccessKeyId;
222 92 : if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
223 : osAccessKeyId))
224 : {
225 4 : 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 9 : 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 9 : if (!STARTS_WITH(pszErrorMsg, "<?xml"))
287 : {
288 3 : if (bSetError)
289 : {
290 1 : VSIError(VSIE_AWSError, "Invalid AWS response: %s", pszErrorMsg);
291 : }
292 3 : return false;
293 : }
294 :
295 6 : CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
296 6 : if (psTree == nullptr)
297 : {
298 1 : if (bSetError)
299 : {
300 1 : VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
301 : pszErrorMsg);
302 : }
303 1 : return false;
304 : }
305 :
306 5 : const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
307 5 : if (pszCode == nullptr)
308 : {
309 1 : CPLDestroyXMLNode(psTree);
310 1 : if (bSetError)
311 : {
312 1 : VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
313 : pszErrorMsg);
314 : }
315 1 : return false;
316 : }
317 :
318 4 : if (EQUAL(pszCode, "AccessDenied"))
319 : {
320 : const char *pszEndpoint =
321 1 : CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
322 1 : if (pszEndpoint && pszEndpoint != m_osEndpoint)
323 : {
324 1 : SetEndpoint(pszEndpoint);
325 1 : CPLDebug("OSS", "Switching to endpoint %s", m_osEndpoint.c_str());
326 1 : CPLDestroyXMLNode(psTree);
327 :
328 1 : VSIOSSUpdateParams::UpdateMapFromHandle(this);
329 :
330 1 : return true;
331 : }
332 : }
333 :
334 3 : if (bSetError)
335 : {
336 : // Translate AWS errors into VSI errors.
337 : const char *pszMessage =
338 3 : CPLGetXMLValue(psTree, "=Error.Message", nullptr);
339 :
340 3 : if (pszMessage == nullptr)
341 : {
342 3 : VSIError(VSIE_AWSError, "%s", pszErrorMsg);
343 : }
344 0 : else if (EQUAL(pszCode, "AccessDenied"))
345 : {
346 0 : VSIError(VSIE_AWSAccessDenied, "%s", pszMessage);
347 : }
348 0 : else if (EQUAL(pszCode, "NoSuchBucket"))
349 : {
350 0 : VSIError(VSIE_AWSBucketNotFound, "%s", pszMessage);
351 : }
352 0 : else if (EQUAL(pszCode, "NoSuchKey"))
353 : {
354 0 : VSIError(VSIE_AWSObjectNotFound, "%s", pszMessage);
355 : }
356 0 : else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
357 : {
358 0 : VSIError(VSIE_AWSSignatureDoesNotMatch, "%s", pszMessage);
359 : }
360 : else
361 : {
362 0 : VSIError(VSIE_AWSError, "%s", pszMessage);
363 : }
364 : }
365 :
366 3 : CPLDestroyXMLNode(psTree);
367 :
368 3 : return false;
369 : }
370 :
371 : /************************************************************************/
372 : /* SetEndpoint() */
373 : /************************************************************************/
374 :
375 8 : void VSIOSSHandleHelper::SetEndpoint(const std::string &osStr)
376 : {
377 8 : m_osEndpoint = osStr;
378 8 : RebuildURL();
379 8 : }
380 :
381 : /************************************************************************/
382 : /* GetSignedURL() */
383 : /************************************************************************/
384 :
385 1 : std::string VSIOSSHandleHelper::GetSignedURL(CSLConstList papszOptions)
386 : {
387 1 : GIntBig nStartDate = static_cast<GIntBig>(time(nullptr));
388 1 : const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
389 1 : if (pszStartDate)
390 : {
391 : int nYear, nMonth, nDay, nHour, nMin, nSec;
392 1 : if (sscanf(pszStartDate, "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
393 1 : &nDay, &nHour, &nMin, &nSec) == 6)
394 : {
395 : struct tm brokendowntime;
396 1 : brokendowntime.tm_year = nYear - 1900;
397 1 : brokendowntime.tm_mon = nMonth - 1;
398 1 : brokendowntime.tm_mday = nDay;
399 1 : brokendowntime.tm_hour = nHour;
400 1 : brokendowntime.tm_min = nMin;
401 1 : brokendowntime.tm_sec = nSec;
402 1 : nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
403 : }
404 : }
405 : GIntBig nExpiresIn =
406 : nStartDate +
407 1 : atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
408 : std::string osExpires(CSLFetchNameValueDef(
409 2 : papszOptions, "EXPIRES", CPLSPrintf(CPL_FRMT_GIB, nExpiresIn)));
410 :
411 2 : std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
412 :
413 : std::string osCanonicalizedResource(
414 1 : m_osBucket.empty() ? std::string("/")
415 4 : : "/" + m_osBucket + "/" + m_osObjectKey);
416 :
417 2 : std::string osStringToSign;
418 1 : osStringToSign += osVerb + "\n";
419 1 : osStringToSign += "\n";
420 1 : osStringToSign += "\n";
421 1 : osStringToSign += osExpires + "\n";
422 : // osStringToSign += ; // osCanonicalizedHeaders;
423 1 : osStringToSign += osCanonicalizedResource;
424 : #ifdef DEBUG_VERBOSE
425 : CPLDebug("OSS", "osStringToSign = %s", osStringToSign.c_str());
426 : #endif
427 :
428 2 : std::string osSignature(GetSignature(osStringToSign, m_osSecretAccessKey));
429 :
430 1 : ResetQueryParameters();
431 : // Note:
432 : // https://www.alibabacloud.com/help/doc-detail/31952.htm?spm=a3c0i.o32002en.b99.294.6d70a0fc7cRJfJ
433 : // is wrong on the name of the OSSAccessKeyId parameter !
434 1 : AddQueryParameter("OSSAccessKeyId", m_osAccessKeyId);
435 1 : AddQueryParameter("Expires", osExpires);
436 1 : AddQueryParameter("Signature", osSignature);
437 2 : return m_osURL;
438 : }
439 :
440 : /************************************************************************/
441 : /* UpdateMapFromHandle() */
442 : /************************************************************************/
443 :
444 : std::mutex VSIOSSUpdateParams::gsMutex{};
445 :
446 : std::map<std::string, VSIOSSUpdateParams>
447 : VSIOSSUpdateParams::goMapBucketsToOSSParams{};
448 :
449 1 : void VSIOSSUpdateParams::UpdateMapFromHandle(
450 : VSIOSSHandleHelper *poOSSHandleHelper)
451 : {
452 1 : std::lock_guard<std::mutex> guard(gsMutex);
453 :
454 1 : goMapBucketsToOSSParams[poOSSHandleHelper->GetBucket()] =
455 2 : VSIOSSUpdateParams(poOSSHandleHelper);
456 1 : }
457 :
458 : /************************************************************************/
459 : /* UpdateHandleFromMap() */
460 : /************************************************************************/
461 :
462 87 : void VSIOSSUpdateParams::UpdateHandleFromMap(
463 : VSIOSSHandleHelper *poOSSHandleHelper)
464 : {
465 174 : std::lock_guard<std::mutex> guard(gsMutex);
466 :
467 : std::map<std::string, VSIOSSUpdateParams>::iterator oIter =
468 87 : goMapBucketsToOSSParams.find(poOSSHandleHelper->GetBucket());
469 87 : if (oIter != goMapBucketsToOSSParams.end())
470 : {
471 7 : oIter->second.UpdateHandlerHelper(poOSSHandleHelper);
472 : }
473 87 : }
474 :
475 : /************************************************************************/
476 : /* ClearCache() */
477 : /************************************************************************/
478 :
479 1531 : void VSIOSSUpdateParams::ClearCache()
480 : {
481 3062 : std::lock_guard<std::mutex> guard(gsMutex);
482 :
483 1531 : goMapBucketsToOSSParams.clear();
484 1531 : }
485 :
486 : #endif // HAVE_CURL
487 :
488 : //! @endcond
|