1# -*- coding: utf-8 -*- 2# Copyright 2013 Google Inc. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Gsutil API delegator for interacting with cloud storage providers.""" 16 17from __future__ import absolute_import 18from __future__ import print_function 19from __future__ import division 20from __future__ import unicode_literals 21 22import boto 23from boto import config 24from gslib.cloud_api import ArgumentException 25from gslib.cloud_api import CloudApi 26from gslib.cs_api_map import ApiMapConstants 27from gslib.cs_api_map import ApiSelector 28from gslib.exception import CommandException 29 30 31class CloudApiDelegator(CloudApi): 32 """Class that handles delegating requests to gsutil Cloud API implementations. 33 34 This class is responsible for determining at runtime which gsutil Cloud API 35 implementation should service the request based on the Cloud storage provider, 36 command-level API support, and configuration file override. 37 38 During initialization it takes as an argument a gsutil_api_map which maps 39 providers to their default and supported gsutil Cloud API implementations 40 (see comments in cs_api_map for details). 41 42 Instantiation of multiple delegators per-thread is required for multiprocess 43 and/or multithreaded operations. Calling methods on the same delegator in 44 multiple threads is unsafe. 45 """ 46 47 def __init__(self, 48 bucket_storage_uri_class, 49 gsutil_api_map, 50 logger, 51 status_queue, 52 provider=None, 53 debug=0, 54 trace_token=None, 55 perf_trace_token=None, 56 user_project=None): 57 """Performs necessary setup for delegating cloud storage requests. 58 59 This function has different arguments than the gsutil Cloud API __init__ 60 function because of the delegation responsibilties of this class. 61 62 Args: 63 bucket_storage_uri_class: boto storage_uri class, used by APIs that 64 provide boto translation or mocking. 65 gsutil_api_map: Map of providers and API selector tuples to api classes 66 which can be used to communicate with those providers. 67 logger: logging.logger for outputting log messages. 68 status_queue: Queue for relaying status to UI. 69 provider: Default provider prefix describing cloud storage provider to 70 connect to. 71 debug: Debug level for the API implementation (0..3). 72 trace_token: Apiary trace token to pass to API. 73 perf_trace_token: Performance trace token to use when making API calls. 74 user_project: Project to be billed for this project. 75 """ 76 super(CloudApiDelegator, self).__init__(bucket_storage_uri_class, 77 logger, 78 status_queue, 79 provider=provider, 80 debug=debug, 81 trace_token=trace_token, 82 perf_trace_token=perf_trace_token, 83 user_project=user_project) 84 self.api_map = gsutil_api_map 85 self.prefer_api = boto.config.get('GSUtil', 'prefer_api', '').upper() 86 self.loaded_apis = {} 87 88 if not self.api_map[ApiMapConstants.API_MAP]: 89 raise ArgumentException('No apiclass supplied for gsutil Cloud API map.') 90 91 def _GetApi(self, provider): 92 """Returns a valid CloudApi for use by the caller. 93 94 This function lazy-loads connection and credentials using the API map 95 and credential store provided during class initialization. 96 97 Args: 98 provider: Provider to load API for. If None, class-wide default is used. 99 100 Raises: 101 ArgumentException if there is no matching API available in the API map. 102 103 Returns: 104 Valid API instance that can be used to communicate with the Cloud 105 Storage provider. 106 """ 107 provider = provider or self.provider 108 if not provider: 109 raise ArgumentException('No provider selected for _GetApi') 110 111 provider = str(provider) 112 if provider not in self.loaded_apis: 113 self.loaded_apis[provider] = {} 114 115 api_selector = self.GetApiSelector(provider) 116 if api_selector not in self.loaded_apis[provider]: 117 # Need to load the API. 118 self._LoadApi(provider, api_selector) 119 return self.loaded_apis[provider][api_selector] 120 121 def _LoadApi(self, provider, api_selector): 122 """Loads a CloudApi into the loaded_apis map for this class. 123 124 Args: 125 provider: Provider to load the API for. 126 api_selector: cs_api_map.ApiSelector defining the API type. 127 """ 128 if provider not in self.api_map[ApiMapConstants.API_MAP]: 129 raise ArgumentException( 130 'gsutil Cloud API map contains no entry for provider %s.' % provider) 131 if api_selector not in self.api_map[ApiMapConstants.API_MAP][provider]: 132 raise ArgumentException( 133 'gsutil Cloud API map does not support API %s for provider %s.' % 134 (api_selector, provider)) 135 self.loaded_apis[provider][api_selector] = ( 136 self.api_map[ApiMapConstants.API_MAP][provider][api_selector]( 137 self.bucket_storage_uri_class, 138 self.logger, 139 self.status_queue, 140 provider=provider, 141 debug=self.debug, 142 trace_token=self.trace_token, 143 perf_trace_token=self.perf_trace_token, 144 user_project=self.user_project)) 145 146 def GetApiSelector(self, provider=None): 147 """Returns a cs_api_map.ApiSelector based on input and configuration. 148 149 Args: 150 provider: Provider to return the ApiSelector for. If None, class-wide 151 default is used. 152 153 Returns: 154 cs_api_map.ApiSelector that will be used for calls to the delegator 155 for this provider. 156 """ 157 selected_provider = provider or self.provider 158 if not selected_provider: 159 raise ArgumentException('No provider selected for CloudApi') 160 161 if (selected_provider not in self.api_map[ApiMapConstants.DEFAULT_MAP] or 162 self.api_map[ApiMapConstants.DEFAULT_MAP][selected_provider] not in 163 self.api_map[ApiMapConstants.API_MAP][selected_provider]): 164 raise ArgumentException('No default api available for provider %s' % 165 selected_provider) 166 167 if selected_provider not in self.api_map[ApiMapConstants.SUPPORT_MAP]: 168 raise ArgumentException('No supported apis available for provider %s' % 169 selected_provider) 170 171 api = self.api_map[ApiMapConstants.DEFAULT_MAP][selected_provider] 172 173 using_gs_hmac = ( 174 provider == 'gs' and 175 not config.has_option('Credentials', 'gs_oauth2_refresh_token') and 176 not (config.has_option('Credentials', 'gs_service_client_id') and 177 config.has_option('Credentials', 'gs_service_key_file')) and 178 (config.has_option('Credentials', 'gs_access_key_id') and 179 config.has_option('Credentials', 'gs_secret_access_key'))) 180 181 configured_encryption = (provider == 'gs' and 182 (config.has_option('GSUtil', 'encryption_key') or 183 config.has_option('GSUtil', 'decryption_key1'))) 184 185 if using_gs_hmac and configured_encryption: 186 raise CommandException( 187 'gsutil does not support HMAC credentials with customer-supplied ' 188 'encryption keys (CSEK) or customer-managed KMS encryption keys ' 189 '(CMEK). Please generate and include non-HMAC credentials ' 190 'in your .boto configuration file, or to access public encrypted ' 191 'objects, remove your HMAC credentials.') 192 # If we have only HMAC credentials for Google Cloud Storage, we must use 193 # the XML API as the JSON API does not support HMAC. 194 # 195 # Technically if we have only HMAC credentials, we should still be able to 196 # access public read resources via the JSON API, but the XML API can do 197 # that just as well. It is better to use it than inspect the credentials on 198 # every HTTP call. 199 elif using_gs_hmac: 200 api = ApiSelector.XML 201 # CSEK and CMEK encryption keys are currently only supported in the 202 # JSON API implementation (GcsJsonApi). We can't stop XML API users from 203 # interacting with encrypted objects, since we don't know the object is 204 # encrypted until after the API call is made, but if they specify 205 # configuration values we will use JSON. 206 elif configured_encryption: 207 api = ApiSelector.JSON 208 # Try to force the user's preference to a supported API. 209 elif self.prefer_api in ( 210 self.api_map[ApiMapConstants.SUPPORT_MAP][selected_provider]): 211 api = self.prefer_api 212 return api 213 214 # For function docstrings, see CloudApi class. 215 def GetBucket(self, bucket_name, provider=None, fields=None): 216 return self._GetApi(provider).GetBucket(bucket_name, fields=fields) 217 218 def GetBucketIamPolicy(self, bucket_name, provider=None, fields=None): 219 return self._GetApi(provider).GetBucketIamPolicy(bucket_name, fields=fields) 220 221 def SetBucketIamPolicy(self, bucket_name, policy, provider=None): 222 return self._GetApi(provider).SetBucketIamPolicy(bucket_name, policy) 223 224 def ListBuckets(self, project_id=None, provider=None, fields=None): 225 return self._GetApi(provider).ListBuckets(project_id=project_id, 226 fields=fields) 227 228 def PatchBucket(self, 229 bucket_name, 230 metadata, 231 canned_acl=None, 232 canned_def_acl=None, 233 preconditions=None, 234 provider=None, 235 fields=None): 236 return self._GetApi(provider).PatchBucket(bucket_name, 237 metadata, 238 canned_acl=canned_acl, 239 canned_def_acl=canned_def_acl, 240 preconditions=preconditions, 241 fields=fields) 242 243 def LockRetentionPolicy(self, bucket_name, metageneration, provider=None): 244 return self._GetApi(provider).LockRetentionPolicy(bucket_name, 245 metageneration, 246 provider=provider) 247 248 def CreateBucket(self, 249 bucket_name, 250 project_id=None, 251 metadata=None, 252 provider=None, 253 fields=None): 254 return self._GetApi(provider).CreateBucket(bucket_name, 255 project_id=project_id, 256 metadata=metadata, 257 fields=fields) 258 259 def DeleteBucket(self, bucket_name, preconditions=None, provider=None): 260 return self._GetApi(provider).DeleteBucket(bucket_name, 261 preconditions=preconditions) 262 263 def GetObjectIamPolicy(self, 264 bucket_name, 265 object_name, 266 generation=None, 267 provider=None, 268 fields=None): 269 return self._GetApi(provider).GetObjectIamPolicy(bucket_name, 270 object_name, 271 generation, 272 fields=fields) 273 274 def SetObjectIamPolicy(self, 275 bucket_name, 276 object_name, 277 policy, 278 generation=None, 279 provider=None): 280 return self._GetApi(provider).SetObjectIamPolicy(bucket_name, object_name, 281 policy, generation) 282 283 def ListObjects(self, 284 bucket_name, 285 prefix=None, 286 delimiter=None, 287 all_versions=None, 288 provider=None, 289 fields=None): 290 return self._GetApi(provider).ListObjects(bucket_name, 291 prefix=prefix, 292 delimiter=delimiter, 293 all_versions=all_versions, 294 fields=fields) 295 296 def GetObjectMetadata(self, 297 bucket_name, 298 object_name, 299 generation=None, 300 provider=None, 301 fields=None): 302 return self._GetApi(provider).GetObjectMetadata(bucket_name, 303 object_name, 304 generation=generation, 305 fields=fields) 306 307 def PatchObjectMetadata(self, 308 bucket_name, 309 object_name, 310 metadata, 311 canned_acl=None, 312 generation=None, 313 preconditions=None, 314 provider=None, 315 fields=None): 316 return self._GetApi(provider).PatchObjectMetadata( 317 bucket_name, 318 object_name, 319 metadata, 320 canned_acl=canned_acl, 321 generation=generation, 322 preconditions=preconditions, 323 fields=fields) 324 325 def GetObjectMedia(self, 326 bucket_name, 327 object_name, 328 download_stream, 329 provider=None, 330 generation=None, 331 object_size=None, 332 compressed_encoding=False, 333 download_strategy=CloudApi.DownloadStrategy.ONE_SHOT, 334 start_byte=0, 335 end_byte=None, 336 progress_callback=None, 337 serialization_data=None, 338 digesters=None, 339 decryption_tuple=None): 340 return self._GetApi(provider).GetObjectMedia( 341 bucket_name, 342 object_name, 343 download_stream, 344 compressed_encoding=compressed_encoding, 345 download_strategy=download_strategy, 346 start_byte=start_byte, 347 end_byte=end_byte, 348 generation=generation, 349 object_size=object_size, 350 progress_callback=progress_callback, 351 serialization_data=serialization_data, 352 digesters=digesters, 353 decryption_tuple=decryption_tuple) 354 355 def UploadObject(self, 356 upload_stream, 357 object_metadata, 358 size=None, 359 canned_acl=None, 360 preconditions=None, 361 progress_callback=None, 362 encryption_tuple=None, 363 provider=None, 364 fields=None, 365 gzip_encoded=False): 366 return self._GetApi(provider).UploadObject( 367 upload_stream, 368 object_metadata, 369 size=size, 370 canned_acl=canned_acl, 371 preconditions=preconditions, 372 progress_callback=progress_callback, 373 encryption_tuple=encryption_tuple, 374 fields=fields, 375 gzip_encoded=gzip_encoded) 376 377 def UploadObjectStreaming(self, 378 upload_stream, 379 object_metadata, 380 canned_acl=None, 381 preconditions=None, 382 progress_callback=None, 383 encryption_tuple=None, 384 provider=None, 385 fields=None, 386 gzip_encoded=False): 387 return self._GetApi(provider).UploadObjectStreaming( 388 upload_stream, 389 object_metadata, 390 canned_acl=canned_acl, 391 preconditions=preconditions, 392 progress_callback=progress_callback, 393 encryption_tuple=encryption_tuple, 394 fields=fields, 395 gzip_encoded=gzip_encoded) 396 397 def UploadObjectResumable(self, 398 upload_stream, 399 object_metadata, 400 canned_acl=None, 401 preconditions=None, 402 size=None, 403 serialization_data=None, 404 tracker_callback=None, 405 progress_callback=None, 406 encryption_tuple=None, 407 provider=None, 408 fields=None, 409 gzip_encoded=False): 410 return self._GetApi(provider).UploadObjectResumable( 411 upload_stream, 412 object_metadata, 413 canned_acl=canned_acl, 414 preconditions=preconditions, 415 size=size, 416 serialization_data=serialization_data, 417 tracker_callback=tracker_callback, 418 progress_callback=progress_callback, 419 encryption_tuple=encryption_tuple, 420 fields=fields, 421 gzip_encoded=gzip_encoded) 422 423 def CopyObject(self, 424 src_obj_metadata, 425 dst_obj_metadata, 426 src_generation=None, 427 canned_acl=None, 428 preconditions=None, 429 progress_callback=None, 430 max_bytes_per_call=None, 431 encryption_tuple=None, 432 decryption_tuple=None, 433 provider=None, 434 fields=None): 435 return self._GetApi(provider).CopyObject( 436 src_obj_metadata, 437 dst_obj_metadata, 438 src_generation=src_generation, 439 canned_acl=canned_acl, 440 preconditions=preconditions, 441 progress_callback=progress_callback, 442 max_bytes_per_call=max_bytes_per_call, 443 encryption_tuple=encryption_tuple, 444 decryption_tuple=decryption_tuple, 445 fields=fields) 446 447 def ComposeObject(self, 448 src_objs_metadata, 449 dst_obj_metadata, 450 preconditions=None, 451 encryption_tuple=None, 452 provider=None, 453 fields=None): 454 return self._GetApi(provider).ComposeObject( 455 src_objs_metadata, 456 dst_obj_metadata, 457 preconditions=preconditions, 458 encryption_tuple=encryption_tuple, 459 fields=fields) 460 461 def DeleteObject(self, 462 bucket_name, 463 object_name, 464 preconditions=None, 465 generation=None, 466 provider=None): 467 return self._GetApi(provider).DeleteObject(bucket_name, 468 object_name, 469 preconditions=preconditions, 470 generation=generation) 471 472 def WatchBucket(self, 473 bucket_name, 474 address, 475 channel_id, 476 token=None, 477 provider=None, 478 fields=None): 479 return self._GetApi(provider).WatchBucket(bucket_name, 480 address, 481 channel_id, 482 token=token, 483 fields=fields) 484 485 def StopChannel(self, channel_id, resource_id, provider=None): 486 return self._GetApi(provider).StopChannel(channel_id, resource_id) 487 488 def ListChannels(self, bucket_name, provider=None): 489 return self._GetApi(provider).ListChannels(bucket_name) 490 491 def GetProjectServiceAccount(self, project_number, provider=None): 492 return self._GetApi(provider).GetProjectServiceAccount(project_number) 493 494 def CreateNotificationConfig(self, 495 bucket_name, 496 pubsub_topic, 497 payload_format, 498 event_types=None, 499 custom_attributes=None, 500 object_name_prefix=None, 501 provider=None): 502 return self._GetApi(provider).CreateNotificationConfig( 503 bucket_name, pubsub_topic, payload_format, event_types, 504 custom_attributes, object_name_prefix) 505 506 def DeleteNotificationConfig(self, bucket_name, notification, provider=None): 507 return self._GetApi(provider).DeleteNotificationConfig( 508 bucket_name, notification) 509 510 def ListNotificationConfigs(self, bucket_name, provider=None): 511 return self._GetApi(provider).ListNotificationConfigs(bucket_name) 512 513 def ListBucketAccessControls(self, bucket_name, provider=None): 514 return self._GetApi(provider).ListBucketAccessControls(bucket_name) 515 516 def ListObjectAccessControls(self, bucket_name, object_name, provider=None): 517 return self._GetApi(provider).ListObjectAccessControls( 518 bucket_name, object_name) 519 520 def CreateHmacKey(self, project_id, service_account_email, provider=None): 521 return self._GetApi(provider).CreateHmacKey(project_id, 522 service_account_email) 523 524 def DeleteHmacKey(self, project_id, access_id, provider=None): 525 return self._GetApi(provider).DeleteHmacKey(project_id, access_id) 526 527 def GetHmacKey(self, project_id, access_id, provider=None): 528 return self._GetApi(provider).GetHmacKey(project_id, access_id) 529 530 def ListHmacKeys(self, 531 project_id, 532 service_account_email, 533 show_deleted_keys=False, 534 provider=None): 535 return self._GetApi(provider).ListHmacKeys(project_id, 536 service_account_email, 537 show_deleted_keys) 538 539 def UpdateHmacKey(self, project_id, access_id, state, etag, provider=None): 540 return self._GetApi(provider).UpdateHmacKey(project_id, access_id, state, 541 etag) 542 543 def XmlPassThroughGetAcl(self, storage_url, def_obj_acl=False, provider=None): 544 """XML compatibility function for getting ACLs. 545 546 Args: 547 storage_url: StorageUrl object. 548 def_obj_acl: If true, get the default object ACL on a bucket. 549 provider: Cloud storage provider to connect to. If not present, 550 class-wide default is used. 551 552 Raises: 553 ArgumentException for errors during input validation. 554 ServiceException for errors interacting with cloud storage providers. 555 556 Returns: 557 ACL XML for the resource specified by storage_url. 558 """ 559 return self._GetApi(provider).XmlPassThroughGetAcl(storage_url, 560 def_obj_acl=def_obj_acl) 561 562 def XmlPassThroughSetAcl(self, 563 acl_text, 564 storage_url, 565 canned=True, 566 def_obj_acl=False, 567 provider=None): 568 """XML compatibility function for setting ACLs. 569 570 Args: 571 acl_text: XML ACL or canned ACL string. 572 storage_url: StorageUrl object. 573 canned: If true, acl_text is treated as a canned ACL string. 574 def_obj_acl: If true, set the default object ACL on a bucket. 575 provider: Cloud storage provider to connect to. If not present, 576 class-wide default is used. 577 578 Raises: 579 ArgumentException for errors during input validation. 580 ServiceException for errors interacting with cloud storage providers. 581 582 Returns: 583 None. 584 """ 585 self._GetApi(provider).XmlPassThroughSetAcl(acl_text, 586 storage_url, 587 canned=canned, 588 def_obj_acl=def_obj_acl) 589 590 def XmlPassThroughGetCors(self, storage_url, provider=None): 591 """XML compatibility function for getting CORS configuration on a bucket. 592 593 Args: 594 storage_url: StorageUrl object. 595 provider: Cloud storage provider to connect to. If not present, 596 class-wide default is used. 597 598 Raises: 599 ArgumentException for errors during input validation. 600 ServiceException for errors interacting with cloud storage providers. 601 602 Returns: 603 CORS configuration XML for the bucket specified by storage_url. 604 """ 605 return self._GetApi(provider).XmlPassThroughGetCors(storage_url) 606 607 def XmlPassThroughSetCors(self, cors_text, storage_url, provider=None): 608 """XML compatibility function for setting CORS configuration on a bucket. 609 610 Args: 611 cors_text: Raw CORS XML string. 612 storage_url: StorageUrl object. 613 provider: Cloud storage provider to connect to. If not present, 614 class-wide default is used. 615 616 Raises: 617 ArgumentException for errors during input validation. 618 ServiceException for errors interacting with cloud storage providers. 619 620 Returns: 621 None. 622 """ 623 self._GetApi(provider).XmlPassThroughSetCors(cors_text, storage_url) 624 625 def XmlPassThroughGetLifecycle(self, storage_url, provider=None): 626 """XML compatibility function for getting lifecycle config on a bucket. 627 628 Args: 629 storage_url: StorageUrl object. 630 provider: Cloud storage provider to connect to. If not present, 631 class-wide default is used. 632 633 Raises: 634 ArgumentException for errors during input validation. 635 ServiceException for errors interacting with cloud storage providers. 636 637 Returns: 638 Lifecycle configuration XML for the bucket specified by storage_url. 639 """ 640 return self._GetApi(provider).XmlPassThroughGetLifecycle(storage_url) 641 642 def XmlPassThroughSetLifecycle(self, 643 lifecycle_text, 644 storage_url, 645 provider=None): 646 """XML compatibility function for setting lifecycle config on a bucket. 647 648 Args: 649 lifecycle_text: Raw lifecycle configuration XML string. 650 storage_url: StorageUrl object. 651 provider: Cloud storage provider to connect to. If not present, 652 class-wide default is used. 653 654 Raises: 655 ArgumentException for errors during input validation. 656 ServiceException for errors interacting with cloud storage providers. 657 658 Returns: 659 None. 660 """ 661 self._GetApi(provider).XmlPassThroughSetLifecycle(lifecycle_text, 662 storage_url) 663 664 def XmlPassThroughGetLogging(self, storage_url, provider=None): 665 """XML compatibility function for getting logging configuration on a bucket. 666 667 Args: 668 storage_url: StorageUrl object. 669 provider: Cloud storage provider to connect to. If not present, 670 class-wide default is used. 671 672 Raises: 673 ArgumentException for errors during input validation. 674 ServiceException for errors interacting with cloud storage providers. 675 676 Returns: 677 Logging configuration XML for the bucket specified by storage_url. 678 """ 679 return self._GetApi(provider).XmlPassThroughGetLogging(storage_url) 680 681 def XmlPassThroughSetTagging(self, tags_text, storage_url, provider=None): 682 """XML compatibility function for setting tagging configuration on a bucket. 683 684 This passthrough provides support for setting a tagging configuration 685 (equivalent to a label configuration) on a cloud bucket. 686 687 Args: 688 tags_text: Raw tagging configuration XML string. 689 storage_url: StorageUrl object. 690 provider: Cloud storage provider to connect to. If not present, 691 class-wide default is used. 692 693 Raises: 694 ArgumentException for errors during input validation. 695 ServiceException for errors interacting with cloud storage providers. 696 697 Returns: 698 None. 699 """ 700 return self._GetApi(provider).XmlPassThroughSetTagging( 701 tags_text, storage_url) 702 703 def XmlPassThroughGetTagging(self, storage_url, provider=None): 704 """XML compatibility function for getting tagging configuration on a bucket. 705 706 Args: 707 storage_url: StorageUrl object. 708 provider: Cloud storage provider to connect to. If not present, 709 class-wide default is used. 710 711 Raises: 712 ArgumentException for errors during input validation. 713 ServiceException for errors interacting with cloud storage providers. 714 715 Returns: 716 Tagging configuration XML for the bucket specified by storage_url. 717 """ 718 return self._GetApi(provider).XmlPassThroughGetTagging(storage_url) 719 720 def XmlPassThroughGetWebsite(self, storage_url, provider=None): 721 """XML compatibility function for getting website configuration on a bucket. 722 723 Args: 724 storage_url: StorageUrl object. 725 provider: Cloud storage provider to connect to. If not present, 726 class-wide default is used. 727 728 Raises: 729 ArgumentException for errors during input validation. 730 ServiceException for errors interacting with cloud storage providers. 731 732 Returns: 733 Website configuration XML for the bucket specified by storage_url. 734 """ 735 return self._GetApi(provider).XmlPassThroughGetWebsite(storage_url) 736