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