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