Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for Google Cloud Storage
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-2018, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "cpl_http.h"
15 : #include "cpl_minixml.h"
16 : #include "cpl_json.h"
17 : #include "cpl_vsil_curl_priv.h"
18 : #include "cpl_vsil_curl_class.h"
19 :
20 : #include <errno.h>
21 :
22 : #include <algorithm>
23 : #include <set>
24 : #include <map>
25 : #include <memory>
26 :
27 : #include "cpl_google_cloud.h"
28 :
29 : // To avoid aliasing to GetDiskFreeSpace to GetDiskFreeSpaceA on Windows
30 : #ifdef GetDiskFreeSpace
31 : #undef GetDiskFreeSpace
32 : #endif
33 :
34 : #ifndef HAVE_CURL
35 :
36 : void VSIInstallGSFileHandler(void)
37 : {
38 : // Not supported.
39 : }
40 :
41 : #else
42 :
43 : //! @cond Doxygen_Suppress
44 : #ifndef DOXYGEN_SKIP
45 :
46 : #define ENABLE_DEBUG 0
47 :
48 : #define unchecked_curl_easy_setopt(handle, opt, param) \
49 : CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
50 :
51 : namespace cpl
52 : {
53 :
54 : /************************************************************************/
55 : /* VSIGSFSHandler */
56 : /************************************************************************/
57 :
58 : class VSIGSFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload
59 : {
60 : CPL_DISALLOW_COPY_ASSIGN(VSIGSFSHandler)
61 : const std::string m_osPrefix;
62 :
63 : protected:
64 : VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
65 :
66 49 : const char *GetDebugKey() const override
67 : {
68 49 : return "GS";
69 : }
70 :
71 578 : std::string GetFSPrefix() const override
72 : {
73 578 : return m_osPrefix;
74 : }
75 :
76 : std::string
77 : GetURLFromFilename(const std::string &osFilename) const override;
78 :
79 : IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
80 : bool bAllowNoObject) override;
81 :
82 : void ClearCache() override;
83 :
84 0 : bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
85 : {
86 0 : return STARTS_WITH(pszHeaderName, "x-goog-");
87 : }
88 :
89 : VSIVirtualHandleUniquePtr
90 : CreateWriteHandle(const char *pszFilename,
91 : CSLConstList papszOptions) override;
92 :
93 0 : GIntBig GetDiskFreeSpace(const char * /* pszDirname */) override
94 : {
95 : // There is no limit per bucket, but a 5 TiB limit per object.
96 0 : return static_cast<GIntBig>(5) * 1024 * 1024 * 1024 * 1024;
97 : }
98 :
99 : public:
100 1691 : explicit VSIGSFSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
101 : {
102 1691 : }
103 :
104 : ~VSIGSFSHandler() override;
105 :
106 : const char *GetOptions() override;
107 :
108 : char *GetSignedURL(const char *pszFilename,
109 : CSLConstList papszOptions) override;
110 :
111 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
112 : CSLConstList papszOptions) override;
113 :
114 : bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
115 : const char *pszDomain,
116 : CSLConstList papszOptions) override;
117 :
118 : int *UnlinkBatch(CSLConstList papszFiles) override;
119 : int RmdirRecursive(const char *pszDirname) override;
120 :
121 : std::string
122 : GetStreamingFilename(const std::string &osFilename) const override;
123 :
124 0 : VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
125 : {
126 0 : return new VSIGSFSHandler(pszPrefix);
127 : }
128 :
129 1 : bool SupportsMultipartAbort() const override
130 : {
131 1 : return true;
132 : }
133 : };
134 :
135 : /************************************************************************/
136 : /* VSIGSHandle */
137 : /************************************************************************/
138 :
139 : class VSIGSHandle final : public IVSIS3LikeHandle
140 : {
141 : CPL_DISALLOW_COPY_ASSIGN(VSIGSHandle)
142 :
143 : VSIGSHandleHelper *m_poHandleHelper = nullptr;
144 :
145 : protected:
146 : struct curl_slist *GetCurlHeaders(const std::string &osVerb,
147 : struct curl_slist *psHeaders) override;
148 :
149 : public:
150 : VSIGSHandle(VSIGSFSHandler *poFS, const char *pszFilename,
151 : VSIGSHandleHelper *poHandleHelper);
152 : ~VSIGSHandle() override;
153 : };
154 :
155 : /************************************************************************/
156 : /* ~VSIGSFSHandler() */
157 : /************************************************************************/
158 :
159 2244 : VSIGSFSHandler::~VSIGSFSHandler()
160 : {
161 1122 : VSICurlFilesystemHandlerBase::ClearCache();
162 2244 : }
163 :
164 : /************************************************************************/
165 : /* ClearCache() */
166 : /************************************************************************/
167 :
168 331 : void VSIGSFSHandler::ClearCache()
169 : {
170 331 : VSICurlFilesystemHandlerBase::ClearCache();
171 :
172 331 : VSIGSHandleHelper::ClearCache();
173 331 : }
174 :
175 : /************************************************************************/
176 : /* CreateFileHandle() */
177 : /************************************************************************/
178 :
179 35 : VSICurlHandle *VSIGSFSHandler::CreateFileHandle(const char *pszFilename)
180 : {
181 70 : VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
182 105 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
183 35 : if (poHandleHelper == nullptr)
184 5 : return nullptr;
185 30 : return new VSIGSHandle(this, pszFilename, poHandleHelper);
186 : }
187 :
188 : /************************************************************************/
189 : /* GetOptions() */
190 : /************************************************************************/
191 :
192 2 : const char *VSIGSFSHandler::GetOptions()
193 : {
194 : static std::string osOptions(
195 2 : std::string("<Options>")
196 : .append(
197 : " <Option name='GS_SECRET_ACCESS_KEY' type='string' "
198 : "description='Secret access key. To use with "
199 : "GS_ACCESS_KEY_ID'/>"
200 : " <Option name='GS_ACCESS_KEY_ID' type='string' "
201 : "description='Access key id'/>"
202 : " <Option name='GS_NO_SIGN_REQUEST' type='boolean' "
203 : "description='Whether to disable signing of requests' "
204 : "default='NO'/>"
205 : " <Option name='GS_OAUTH2_REFRESH_TOKEN' type='string' "
206 : "description='OAuth2 refresh token. For OAuth2 client "
207 : "authentication. "
208 : "To use with GS_OAUTH2_CLIENT_ID and GS_OAUTH2_CLIENT_SECRET'/>"
209 : " <Option name='GS_OAUTH2_CLIENT_ID' type='string' "
210 : "description='OAuth2 client id for OAuth2 client "
211 : "authentication'/>"
212 : " <Option name='GS_OAUTH2_CLIENT_SECRET' type='string' "
213 : "description='OAuth2 client secret for OAuth2 client "
214 : "authentication'/>"
215 : " <Option name='GS_OAUTH2_PRIVATE_KEY' type='string' "
216 : "description='Private key for OAuth2 service account "
217 : "authentication. "
218 : "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
219 : " <Option name='GS_OAUTH2_PRIVATE_KEY_FILE' type='string' "
220 : "description='Filename that contains private key for OAuth2 "
221 : "service "
222 : "account authentication. "
223 : "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
224 : " <Option name='GS_OAUTH2_CLIENT_EMAIL' type='string' "
225 : "description='Client email to use with OAuth2 service account "
226 : "authentication'/>"
227 : " <Option name='GS_OAUTH2_SCOPE' type='string' "
228 : "description='OAuth2 authorization scope' "
229 : "default='https://www.googleapis.com/auth/"
230 : "devstorage.read_write'/>"
231 : " <Option name='CPL_MACHINE_IS_GCE' type='boolean' "
232 : "description='Whether the current machine is a Google Compute "
233 : "Engine "
234 : "instance' default='NO'/>"
235 : " <Option name='CPL_GCE_CHECK_LOCAL_FILES' type='boolean' "
236 : "description='Whether to check system logs to determine "
237 : "if current machine is a GCE instance' default='YES'/>"
238 : "description='Filename that contains AWS configuration' "
239 : "default='~/.aws/config'/>"
240 : " <Option name='CPL_GS_CREDENTIALS_FILE' type='string' "
241 : "description='Filename that contains Google Storage "
242 : "credentials' "
243 : "default='~/.boto'/>"
244 : " <Option name='VSIGS_CHUNK_SIZE' type='int' "
245 : "description='Size in MiB for chunks of files that are "
246 : "uploaded. The"
247 1 : "default value allows for files up to ")
248 1 : .append(CPLSPrintf("%d", GetDefaultPartSizeInMiB() *
249 1 : GetMaximumPartCount() / 1024))
250 1 : .append("GiB each' default='")
251 1 : .append(CPLSPrintf("%d", GetDefaultPartSizeInMiB()))
252 1 : .append("' min='")
253 1 : .append(CPLSPrintf("%d", GetMinimumPartSizeInMiB()))
254 1 : .append("' max='")
255 1 : .append(CPLSPrintf("%d", GetMaximumPartSizeInMiB()))
256 1 : .append("'/>")
257 1 : .append(VSICurlFilesystemHandlerBase::GetOptionsStatic())
258 3 : .append("</Options>"));
259 2 : return osOptions.c_str();
260 : }
261 :
262 : /************************************************************************/
263 : /* GetSignedURL() */
264 : /************************************************************************/
265 :
266 6 : char *VSIGSFSHandler::GetSignedURL(const char *pszFilename,
267 : CSLConstList papszOptions)
268 : {
269 6 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
270 0 : return nullptr;
271 :
272 12 : VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
273 18 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), nullptr,
274 : papszOptions);
275 6 : if (poHandleHelper == nullptr)
276 : {
277 1 : return nullptr;
278 : }
279 :
280 5 : std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
281 :
282 5 : delete poHandleHelper;
283 5 : return osRet.empty() ? nullptr : CPLStrdup(osRet.c_str());
284 : }
285 :
286 : /************************************************************************/
287 : /* GetURLFromFilename() */
288 : /************************************************************************/
289 :
290 : std::string
291 11 : VSIGSFSHandler::GetURLFromFilename(const std::string &osFilename) const
292 : {
293 : const std::string osFilenameWithoutPrefix =
294 22 : osFilename.substr(GetFSPrefix().size());
295 : auto poHandleHelper =
296 : std::unique_ptr<VSIGSHandleHelper>(VSIGSHandleHelper::BuildFromURI(
297 22 : osFilenameWithoutPrefix.c_str(), GetFSPrefix().c_str()));
298 11 : if (poHandleHelper == nullptr)
299 1 : return std::string();
300 10 : return poHandleHelper->GetURL();
301 : }
302 :
303 : /************************************************************************/
304 : /* CreateHandleHelper() */
305 : /************************************************************************/
306 :
307 14 : IVSIS3LikeHandleHelper *VSIGSFSHandler::CreateHandleHelper(const char *pszURI,
308 : bool)
309 : {
310 14 : return VSIGSHandleHelper::BuildFromURI(pszURI, GetFSPrefix().c_str());
311 : }
312 :
313 : /************************************************************************/
314 : /* CreateWriteHandle() */
315 : /************************************************************************/
316 :
317 : VSIVirtualHandleUniquePtr
318 1 : VSIGSFSHandler::CreateWriteHandle(const char *pszFilename,
319 : CSLConstList papszOptions)
320 : {
321 : auto poHandleHelper =
322 1 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false);
323 1 : if (poHandleHelper == nullptr)
324 0 : return nullptr;
325 : auto poHandle = std::make_unique<VSIMultipartWriteHandle>(
326 2 : this, pszFilename, poHandleHelper, papszOptions);
327 1 : if (!poHandle->IsOK())
328 : {
329 0 : return nullptr;
330 : }
331 1 : return VSIVirtualHandleUniquePtr(poHandle.release());
332 : }
333 :
334 : /************************************************************************/
335 : /* GetFileMetadata() */
336 : /************************************************************************/
337 :
338 7 : char **VSIGSFSHandler::GetFileMetadata(const char *pszFilename,
339 : const char *pszDomain,
340 : CSLConstList papszOptions)
341 : {
342 7 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
343 0 : return nullptr;
344 :
345 7 : if (pszDomain == nullptr)
346 : {
347 : // Handle case of requesting GetFileMetadata() on the bucket root
348 3 : std::string osFilename(pszFilename);
349 3 : if (osFilename.back() == '/')
350 3 : osFilename.pop_back();
351 3 : if (osFilename.find('/', GetFSPrefix().size()) == std::string::npos)
352 : {
353 : const std::string osBucket =
354 6 : osFilename.substr(GetFSPrefix().size());
355 : const std::string osResource =
356 9 : std::string("storage/v1/b/").append(osBucket);
357 :
358 : auto poHandleHelper = std::unique_ptr<VSIGSHandleHelper>(
359 : VSIGSHandleHelper::BuildFromURI(osResource.c_str(),
360 3 : GetFSPrefix().c_str(),
361 9 : osBucket.c_str()));
362 3 : if (!poHandleHelper)
363 0 : return nullptr;
364 :
365 : // Check if OAuth2 is used externally and a bearer token is passed
366 : // as a header in path-specific options
367 : const CPLStringList aosHTTPOptions(
368 6 : CPLHTTPGetOptionsFromEnv(pszFilename));
369 3 : bool bUsingBearerToken = false;
370 3 : const char *pszHeaders = aosHTTPOptions.FetchNameValue("HEADERS");
371 3 : if (pszHeaders && strstr(pszHeaders, "Authorization: Bearer "))
372 1 : bUsingBearerToken = true;
373 :
374 : // The JSON API cannot be used with HMAC keys
375 3 : if (poHandleHelper->UsesHMACKey() && !bUsingBearerToken)
376 : {
377 1 : CPLDebug(GetDebugKey(),
378 : "GetFileMetadata() on bucket "
379 : "only available for OAuth2 authentication");
380 1 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
381 1 : pszFilename, pszDomain, papszOptions);
382 : }
383 :
384 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
385 4 : NetworkStatisticsAction oContextAction("GetFileMetadata");
386 :
387 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
388 4 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
389 :
390 : bool bRetry;
391 4 : CPLStringList aosResult;
392 2 : do
393 : {
394 2 : bRetry = false;
395 2 : CURL *hCurlHandle = curl_easy_init();
396 :
397 : struct curl_slist *headers =
398 4 : static_cast<struct curl_slist *>(CPLHTTPSetOptions(
399 2 : hCurlHandle, poHandleHelper->GetURL().c_str(),
400 : aosHTTPOptions.List()));
401 2 : headers = poHandleHelper->GetCurlHeaders("GET", headers);
402 :
403 4 : CurlRequestHelper requestHelper;
404 2 : const long response_code = requestHelper.perform(
405 2 : hCurlHandle, headers, this, poHandleHelper.get());
406 :
407 2 : NetworkStatisticsLogger::LogGET(
408 : requestHelper.sWriteFuncData.nSize);
409 :
410 2 : if (response_code != 200 ||
411 2 : requestHelper.sWriteFuncData.pBuffer == nullptr)
412 : {
413 : // Look if we should attempt a retry
414 0 : if (oRetryContext.CanRetry(
415 : static_cast<int>(response_code),
416 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
417 : requestHelper.szCurlErrBuf))
418 : {
419 0 : CPLError(CE_Warning, CPLE_AppDefined,
420 : "HTTP error code: %d - %s. "
421 : "Retrying again in %.1f secs",
422 : static_cast<int>(response_code),
423 0 : poHandleHelper->GetURL().c_str(),
424 : oRetryContext.GetCurrentDelay());
425 0 : CPLSleep(oRetryContext.GetCurrentDelay());
426 0 : bRetry = true;
427 : }
428 : else
429 : {
430 0 : CPLDebug(GetDebugKey(), "%s",
431 0 : requestHelper.sWriteFuncData.pBuffer
432 : ? requestHelper.sWriteFuncData.pBuffer
433 : : "(null)");
434 0 : CPLError(CE_Failure, CPLE_AppDefined,
435 : "GetFileMetadata failed");
436 : }
437 : }
438 : else
439 : {
440 4 : CPLJSONDocument oDoc;
441 6 : if (oDoc.LoadMemory(
442 : reinterpret_cast<const GByte *>(
443 2 : requestHelper.sWriteFuncData.pBuffer),
444 : static_cast<int>(
445 4 : requestHelper.sWriteFuncData.nSize)) &&
446 4 : oDoc.GetRoot().GetType() == CPLJSONObject::Type::Object)
447 : {
448 4 : for (const auto &oObj : oDoc.GetRoot().GetChildren())
449 : {
450 4 : aosResult.SetNameValue(oObj.GetName().c_str(),
451 6 : oObj.ToString().c_str());
452 : }
453 : }
454 : else
455 : {
456 : // Shouldn't happen normally
457 : aosResult.SetNameValue(
458 0 : "DATA", requestHelper.sWriteFuncData.pBuffer);
459 : }
460 : }
461 :
462 2 : curl_easy_cleanup(hCurlHandle);
463 : } while (bRetry);
464 :
465 2 : return aosResult.StealList();
466 : }
467 : }
468 :
469 4 : if (pszDomain == nullptr || !EQUAL(pszDomain, "ACL"))
470 : {
471 2 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
472 2 : pszFilename, pszDomain, papszOptions);
473 : }
474 :
475 : auto poHandleHelper =
476 4 : std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
477 10 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
478 2 : if (!poHandleHelper)
479 0 : return nullptr;
480 :
481 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
482 4 : NetworkStatisticsAction oContextAction("GetFileMetadata");
483 :
484 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
485 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
486 4 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
487 :
488 : bool bRetry;
489 4 : CPLStringList aosResult;
490 2 : do
491 : {
492 2 : bRetry = false;
493 2 : CURL *hCurlHandle = curl_easy_init();
494 2 : poHandleHelper->AddQueryParameter("acl", "");
495 :
496 : struct curl_slist *headers = static_cast<struct curl_slist *>(
497 2 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
498 : aosHTTPOptions.List()));
499 2 : headers = poHandleHelper->GetCurlHeaders("GET", headers);
500 :
501 4 : CurlRequestHelper requestHelper;
502 2 : const long response_code = requestHelper.perform(
503 : hCurlHandle, headers, this, poHandleHelper.get());
504 :
505 2 : NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
506 :
507 2 : if (response_code != 200 ||
508 1 : requestHelper.sWriteFuncData.pBuffer == nullptr)
509 : {
510 : // Look if we should attempt a retry
511 1 : if (oRetryContext.CanRetry(
512 : static_cast<int>(response_code),
513 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
514 : requestHelper.szCurlErrBuf))
515 : {
516 0 : CPLError(CE_Warning, CPLE_AppDefined,
517 : "HTTP error code: %d - %s. "
518 : "Retrying again in %.1f secs",
519 : static_cast<int>(response_code),
520 0 : poHandleHelper->GetURL().c_str(),
521 : oRetryContext.GetCurrentDelay());
522 0 : CPLSleep(oRetryContext.GetCurrentDelay());
523 0 : bRetry = true;
524 : }
525 : else
526 : {
527 1 : CPLDebug(GetDebugKey(), "%s",
528 1 : requestHelper.sWriteFuncData.pBuffer
529 : ? requestHelper.sWriteFuncData.pBuffer
530 : : "(null)");
531 1 : CPLError(CE_Failure, CPLE_AppDefined, "GetFileMetadata failed");
532 : }
533 : }
534 : else
535 : {
536 1 : aosResult.SetNameValue("XML", requestHelper.sWriteFuncData.pBuffer);
537 : }
538 :
539 2 : curl_easy_cleanup(hCurlHandle);
540 : } while (bRetry);
541 2 : return aosResult.StealList();
542 : }
543 :
544 : /************************************************************************/
545 : /* SetFileMetadata() */
546 : /************************************************************************/
547 :
548 5 : bool VSIGSFSHandler::SetFileMetadata(const char *pszFilename,
549 : CSLConstList papszMetadata,
550 : const char *pszDomain,
551 : CSLConstList /* papszOptions */)
552 : {
553 5 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
554 0 : return false;
555 :
556 5 : if (pszDomain == nullptr ||
557 5 : !(EQUAL(pszDomain, "HEADERS") || EQUAL(pszDomain, "ACL")))
558 : {
559 1 : CPLError(CE_Failure, CPLE_NotSupported,
560 : "Only HEADERS and ACL domain are supported");
561 1 : return false;
562 : }
563 :
564 4 : if (EQUAL(pszDomain, "HEADERS"))
565 : {
566 1 : return CopyObject(pszFilename, pszFilename, papszMetadata) == 0;
567 : }
568 :
569 3 : const char *pszXML = CSLFetchNameValue(papszMetadata, "XML");
570 3 : if (pszXML == nullptr)
571 : {
572 1 : CPLError(CE_Failure, CPLE_AppDefined, "XML key is missing in metadata");
573 1 : return false;
574 : }
575 :
576 : auto poHandleHelper =
577 4 : std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
578 10 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
579 2 : if (!poHandleHelper)
580 0 : return false;
581 :
582 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
583 4 : NetworkStatisticsAction oContextAction("SetFileMetadata");
584 :
585 : bool bRetry;
586 2 : bool bRet = false;
587 :
588 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
589 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
590 2 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
591 :
592 2 : do
593 : {
594 2 : bRetry = false;
595 2 : CURL *hCurlHandle = curl_easy_init();
596 2 : poHandleHelper->AddQueryParameter("acl", "");
597 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
598 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, pszXML);
599 :
600 : struct curl_slist *headers = static_cast<struct curl_slist *>(
601 2 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
602 : aosHTTPOptions.List()));
603 2 : headers = curl_slist_append(headers, "Content-Type: application/xml");
604 4 : headers = poHandleHelper->GetCurlHeaders("PUT", headers, pszXML,
605 2 : strlen(pszXML));
606 2 : NetworkStatisticsLogger::LogPUT(strlen(pszXML));
607 :
608 4 : CurlRequestHelper requestHelper;
609 2 : const long response_code = requestHelper.perform(
610 : hCurlHandle, headers, this, poHandleHelper.get());
611 :
612 2 : if (response_code != 200)
613 : {
614 : // Look if we should attempt a retry
615 1 : if (oRetryContext.CanRetry(
616 : static_cast<int>(response_code),
617 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
618 : requestHelper.szCurlErrBuf))
619 : {
620 0 : CPLError(CE_Warning, CPLE_AppDefined,
621 : "HTTP error code: %d - %s. "
622 : "Retrying again in %.1f secs",
623 : static_cast<int>(response_code),
624 0 : poHandleHelper->GetURL().c_str(),
625 : oRetryContext.GetCurrentDelay());
626 0 : CPLSleep(oRetryContext.GetCurrentDelay());
627 0 : bRetry = true;
628 : }
629 : else
630 : {
631 1 : CPLDebug(GetDebugKey(), "%s",
632 1 : requestHelper.sWriteFuncData.pBuffer
633 : ? requestHelper.sWriteFuncData.pBuffer
634 : : "(null)");
635 1 : CPLError(CE_Failure, CPLE_AppDefined, "SetFileMetadata failed");
636 : }
637 : }
638 : else
639 : {
640 1 : bRet = true;
641 : }
642 :
643 2 : curl_easy_cleanup(hCurlHandle);
644 : } while (bRetry);
645 2 : return bRet;
646 : }
647 :
648 : /************************************************************************/
649 : /* UnlinkBatch() */
650 : /************************************************************************/
651 :
652 2 : int *VSIGSFSHandler::UnlinkBatch(CSLConstList papszFiles)
653 : {
654 : // Implemented using
655 : // https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
656 :
657 2 : const char *pszFirstFilename =
658 2 : papszFiles && papszFiles[0] ? papszFiles[0] : nullptr;
659 :
660 2 : bool bUsingBearerToken = false;
661 2 : if (pszFirstFilename)
662 : {
663 : const CPLStringList aosHTTPOptions(
664 4 : CPLHTTPGetOptionsFromEnv(pszFirstFilename));
665 2 : const char *pszHeaders = aosHTTPOptions.FetchNameValue("HEADERS");
666 2 : if (pszHeaders && strstr(pszHeaders, "Authorization: Bearer "))
667 1 : bUsingBearerToken = true;
668 : }
669 :
670 : auto poHandleHelper =
671 : std::unique_ptr<VSIGSHandleHelper>(VSIGSHandleHelper::BuildFromURI(
672 2 : "batch/storage/v1", GetFSPrefix().c_str(),
673 6 : pszFirstFilename &&
674 4 : STARTS_WITH(pszFirstFilename, GetFSPrefix().c_str())
675 6 : ? pszFirstFilename + GetFSPrefix().size()
676 8 : : nullptr));
677 :
678 : // The JSON API cannot be used with HMAC keys
679 2 : if ((poHandleHelper && poHandleHelper->UsesHMACKey()) && !bUsingBearerToken)
680 : {
681 0 : CPLDebug(GetDebugKey(), "UnlinkBatch() has an efficient implementation "
682 : "only for OAuth2 authentication");
683 0 : return VSICurlFilesystemHandlerBase::UnlinkBatch(papszFiles);
684 : }
685 :
686 : int *panRet =
687 2 : static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
688 :
689 2 : if (!poHandleHelper || pszFirstFilename == nullptr)
690 0 : return panRet;
691 :
692 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
693 4 : NetworkStatisticsAction oContextAction("UnlinkBatch");
694 :
695 : // For debug / testing only
696 : const int nBatchSize =
697 2 : std::max(1, std::min(100, atoi(CPLGetConfigOption(
698 2 : "CPL_VSIGS_UNLINK_BATCH_SIZE", "100"))));
699 4 : std::string osPOSTContent;
700 :
701 : const CPLStringList aosHTTPOptions(
702 4 : CPLHTTPGetOptionsFromEnv(pszFirstFilename));
703 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
704 4 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
705 :
706 6 : for (int i = 0; papszFiles && papszFiles[i]; i++)
707 : {
708 4 : CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
709 : const char *pszFilenameWithoutPrefix =
710 4 : papszFiles[i] + GetFSPrefix().size();
711 4 : const char *pszSlash = strchr(pszFilenameWithoutPrefix, '/');
712 4 : if (!pszSlash)
713 0 : return panRet;
714 8 : std::string osBucket;
715 : osBucket.assign(pszFilenameWithoutPrefix,
716 4 : pszSlash - pszFilenameWithoutPrefix);
717 :
718 8 : std::string osResource = "storage/v1/b/";
719 4 : osResource += osBucket;
720 4 : osResource += "/o/";
721 4 : osResource += CPLAWSURLEncode(pszSlash + 1, true);
722 :
723 : #ifdef ADD_AUTH_TO_NESTED_REQUEST
724 : std::string osAuthorization;
725 : std::string osDate;
726 : {
727 : auto poTmpHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
728 : VSIGSHandleHelper::BuildFromURI(osResource.c_str(),
729 : GetFSPrefix().c_str()));
730 : CURL *hCurlHandle = curl_easy_init();
731 : struct curl_slist *subrequest_headers =
732 : static_cast<struct curl_slist *>(CPLHTTPSetOptions(
733 : hCurlHandle, poTmpHandleHelper->GetURL().c_str(),
734 : aosHTTPOptions.List()));
735 : subrequest_headers = poTmpHandleHelper->GetCurlHeaders(
736 : "DELETE", subrequest_headers, nullptr, 0);
737 : for (struct curl_slist *iter = subrequest_headers; iter;
738 : iter = iter->next)
739 : {
740 : if (STARTS_WITH_CI(iter->data, "Authorization: "))
741 : {
742 : osAuthorization = iter->data;
743 : }
744 : else if (STARTS_WITH_CI(iter->data, "Date: "))
745 : {
746 : osDate = iter->data;
747 : }
748 : }
749 : curl_slist_free_all(subrequest_headers);
750 : curl_easy_cleanup(hCurlHandle);
751 : }
752 : #endif
753 :
754 4 : osPOSTContent += "--===============7330845974216740156==\r\n";
755 4 : osPOSTContent += "Content-Type: application/http\r\n";
756 4 : osPOSTContent += CPLSPrintf("Content-ID: <%d>\r\n", i + 1);
757 4 : osPOSTContent += "\r\n\r\n";
758 4 : osPOSTContent += "DELETE /";
759 4 : osPOSTContent += osResource;
760 4 : osPOSTContent += " HTTP/1.1\r\n";
761 : #ifdef ADD_AUTH_TO_NESTED_REQUEST
762 : if (!osAuthorization.empty())
763 : {
764 : osPOSTContent += osAuthorization;
765 : osPOSTContent += "\r\n";
766 : }
767 : if (!osDate.empty())
768 : {
769 : osPOSTContent += osDate;
770 : osPOSTContent += "\r\n";
771 : }
772 : #endif
773 4 : osPOSTContent += "\r\n\r\n";
774 :
775 4 : if (((i + 1) % nBatchSize) == 0 || papszFiles[i + 1] == nullptr)
776 : {
777 3 : osPOSTContent += "--===============7330845974216740156==--\r\n";
778 :
779 : #ifdef DEBUG_VERBOSE
780 : CPLDebug(GetDebugKey(), "%s", osPOSTContent.c_str());
781 : #endif
782 :
783 : // Run request
784 : bool bRetry;
785 6 : std::string osResponse;
786 3 : do
787 : {
788 3 : bRetry = false;
789 3 : CURL *hCurlHandle = curl_easy_init();
790 :
791 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
792 : "POST");
793 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
794 : osPOSTContent.c_str());
795 :
796 : struct curl_slist *headers =
797 6 : static_cast<struct curl_slist *>(CPLHTTPSetOptions(
798 3 : hCurlHandle, poHandleHelper->GetURL().c_str(),
799 : aosHTTPOptions.List()));
800 3 : headers = curl_slist_append(
801 : headers,
802 : "Content-Type: multipart/mixed; "
803 : "boundary=\"===============7330845974216740156==\"");
804 6 : headers = poHandleHelper->GetCurlHeaders("POST", headers,
805 3 : osPOSTContent.c_str(),
806 : osPOSTContent.size());
807 :
808 6 : CurlRequestHelper requestHelper;
809 3 : const long response_code = requestHelper.perform(
810 3 : hCurlHandle, headers, this, poHandleHelper.get());
811 :
812 3 : NetworkStatisticsLogger::LogPOST(
813 : osPOSTContent.size(), requestHelper.sWriteFuncData.nSize);
814 :
815 3 : if (response_code != 200 ||
816 3 : requestHelper.sWriteFuncData.pBuffer == nullptr)
817 : {
818 : // Look if we should attempt a retry
819 0 : if (oRetryContext.CanRetry(
820 : static_cast<int>(response_code),
821 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
822 : requestHelper.szCurlErrBuf))
823 : {
824 0 : CPLError(CE_Warning, CPLE_AppDefined,
825 : "HTTP error code: %d - %s. "
826 : "Retrying again in %.1f secs",
827 : static_cast<int>(response_code),
828 0 : poHandleHelper->GetURL().c_str(),
829 : oRetryContext.GetCurrentDelay());
830 0 : CPLSleep(oRetryContext.GetCurrentDelay());
831 0 : bRetry = true;
832 : }
833 : else
834 : {
835 0 : CPLDebug(GetDebugKey(), "%s",
836 0 : requestHelper.sWriteFuncData.pBuffer
837 : ? requestHelper.sWriteFuncData.pBuffer
838 : : "(null)");
839 0 : CPLError(CE_Failure, CPLE_AppDefined,
840 : "DeleteObjects failed");
841 : }
842 : }
843 : else
844 : {
845 : #ifdef DEBUG_VERBOSE
846 : CPLDebug(GetDebugKey(), "%s",
847 : requestHelper.sWriteFuncData.pBuffer);
848 : #endif
849 3 : osResponse = requestHelper.sWriteFuncData.pBuffer;
850 : }
851 :
852 3 : curl_easy_cleanup(hCurlHandle);
853 : } while (bRetry);
854 :
855 : // Mark deleted files
856 107 : for (int j = i + 1 - nBatchSize; j <= i; j++)
857 : {
858 104 : auto nPos = osResponse.find(
859 : CPLSPrintf("Content-ID: <response-%d>", j + 1));
860 104 : if (nPos != std::string::npos)
861 : {
862 4 : nPos = osResponse.find("HTTP/1.1 ", nPos);
863 4 : if (nPos != std::string::npos)
864 : {
865 : const char *pszHTTPCode =
866 4 : osResponse.c_str() + nPos + strlen("HTTP/1.1 ");
867 4 : panRet[j] = (atoi(pszHTTPCode) == 204) ? 1 : 0;
868 : }
869 : }
870 : }
871 :
872 3 : osPOSTContent.clear();
873 : }
874 : }
875 2 : return panRet;
876 : }
877 :
878 : /************************************************************************/
879 : /* RmdirRecursive() */
880 : /************************************************************************/
881 :
882 1 : int VSIGSFSHandler::RmdirRecursive(const char *pszDirname)
883 : {
884 : // For debug / testing only
885 : const int nBatchSize = std::min(
886 1 : 100, atoi(CPLGetConfigOption("CPL_VSIGS_UNLINK_BATCH_SIZE", "100")));
887 :
888 1 : return RmdirRecursiveInternal(pszDirname, nBatchSize);
889 : }
890 :
891 : /************************************************************************/
892 : /* GetStreamingFilename() */
893 : /************************************************************************/
894 :
895 : std::string
896 0 : VSIGSFSHandler::GetStreamingFilename(const std::string &osFilename) const
897 : {
898 0 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
899 0 : return "/vsigs_streaming/" + osFilename.substr(GetFSPrefix().size());
900 0 : return osFilename;
901 : }
902 :
903 : /************************************************************************/
904 : /* VSIGSHandle() */
905 : /************************************************************************/
906 :
907 30 : VSIGSHandle::VSIGSHandle(VSIGSFSHandler *poFSIn, const char *pszFilename,
908 30 : VSIGSHandleHelper *poHandleHelper)
909 30 : : IVSIS3LikeHandle(poFSIn, pszFilename, poHandleHelper->GetURL().c_str()),
910 30 : m_poHandleHelper(poHandleHelper)
911 : {
912 30 : }
913 :
914 : /************************************************************************/
915 : /* ~VSIGSHandle() */
916 : /************************************************************************/
917 :
918 60 : VSIGSHandle::~VSIGSHandle()
919 : {
920 30 : delete m_poHandleHelper;
921 60 : }
922 :
923 : /************************************************************************/
924 : /* GetCurlHeaders() */
925 : /************************************************************************/
926 :
927 24 : struct curl_slist *VSIGSHandle::GetCurlHeaders(const std::string &osVerb,
928 : struct curl_slist *psHeaders)
929 : {
930 24 : return m_poHandleHelper->GetCurlHeaders(osVerb, psHeaders);
931 : }
932 :
933 : } /* end of namespace cpl */
934 :
935 : #endif // DOXYGEN_SKIP
936 : //! @endcond
937 :
938 : /************************************************************************/
939 : /* VSIInstallGSFileHandler() */
940 : /************************************************************************/
941 :
942 : /*!
943 : \brief Install /vsigs/ Google Cloud Storage file system handler
944 : (requires libcurl)
945 :
946 : \verbatim embed:rst
947 : See :ref:`/vsigs/ documentation <vsigs>`
948 : \endverbatim
949 :
950 : @since GDAL 2.2
951 : */
952 :
953 1691 : void VSIInstallGSFileHandler(void)
954 : {
955 1691 : VSIFileManager::InstallHandler("/vsigs/",
956 1691 : new cpl::VSIGSFSHandler("/vsigs/"));
957 1691 : }
958 :
959 : #endif /* HAVE_CURL */
|