| OLD | NEW |
| 1 # -*- coding: utf-8 -*- |
| 1 # Copyright 2012 Google Inc. All Rights Reserved. | 2 # Copyright 2012 Google Inc. All Rights Reserved. |
| 2 # | 3 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. | 5 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at | 6 # You may obtain a copy of the License at |
| 6 # | 7 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # | 9 # |
| 9 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and | 13 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. | 14 # limitations under the License. |
| 15 """Implementation of cors configuration command for GCS buckets.""" |
| 16 |
| 17 from __future__ import absolute_import |
| 14 | 18 |
| 15 import sys | 19 import sys |
| 16 import xml | |
| 17 | 20 |
| 18 from boto import handler | |
| 19 from boto.gs.cors import Cors | |
| 20 from gslib.command import Command | 21 from gslib.command import Command |
| 21 from gslib.command import COMMAND_NAME | 22 from gslib.cs_api_map import ApiSelector |
| 22 from gslib.command import COMMAND_NAME_ALIASES | |
| 23 from gslib.command import FILE_URIS_OK | |
| 24 from gslib.command import MAX_ARGS | |
| 25 from gslib.command import MIN_ARGS | |
| 26 from gslib.command import PROVIDER_URIS_OK | |
| 27 from gslib.command import SUPPORTED_SUB_ARGS | |
| 28 from gslib.command import URIS_START_ARG | |
| 29 from gslib.exception import CommandException | 23 from gslib.exception import CommandException |
| 30 from gslib.help_provider import CreateHelpText | 24 from gslib.help_provider import CreateHelpText |
| 31 from gslib.help_provider import HELP_NAME | 25 from gslib.storage_url import StorageUrlFromString |
| 32 from gslib.help_provider import HELP_NAME_ALIASES | 26 from gslib.third_party.storage_apitools import storage_v1_messages as apitools_m
essages |
| 33 from gslib.help_provider import HELP_ONE_LINE_SUMMARY | 27 from gslib.translation_helper import CorsTranslation |
| 34 from gslib.help_provider import HELP_TEXT | 28 from gslib.translation_helper import REMOVE_CORS_CONFIG |
| 35 from gslib.help_provider import HelpType | |
| 36 from gslib.help_provider import HELP_TYPE | |
| 37 from gslib.help_provider import SUBCOMMAND_HELP_TEXT | |
| 38 from gslib.util import NO_MAX | 29 from gslib.util import NO_MAX |
| 30 from gslib.util import UrlsAreForSingleProvider |
| 31 |
| 39 | 32 |
| 40 _GET_SYNOPSIS = """ | 33 _GET_SYNOPSIS = """ |
| 41 gsutil cors get uri | 34 gsutil cors get url |
| 42 """ | 35 """ |
| 43 | 36 |
| 44 _SET_SYNOPSIS = """ | 37 _SET_SYNOPSIS = """ |
| 45 gsutil cors set cors-xml-file uri... | 38 gsutil cors set cors-json-file url... |
| 46 """ | 39 """ |
| 47 | 40 |
| 48 _GET_DESCRIPTION = """ | 41 _GET_DESCRIPTION = """ |
| 49 <B>GET</B> | 42 <B>GET</B> |
| 50 Gets the CORS configuration for a single bucket. The output from | 43 Gets the CORS configuration for a single bucket. The output from |
| 51 "cors get" can be redirected into a file, edited and then updated using | 44 "cors get" can be redirected into a file, edited and then updated using |
| 52 "cors set". | 45 "cors set". |
| 53 """ | 46 """ |
| 54 | 47 |
| 55 _SET_DESCRIPTION = """ | 48 _SET_DESCRIPTION = """ |
| 56 <B>SET</B> | 49 <B>SET</B> |
| 57 Sets the CORS configuration for one or more buckets. The | 50 Sets the CORS configuration for one or more buckets. The |
| 58 cors-xml-file specified on the command line should be a path to a local | 51 cors-json-file specified on the command line should be a path to a local |
| 59 file containing an XML document as described above. | 52 file containing a JSON document as described above. |
| 60 """ | 53 """ |
| 61 | 54 |
| 62 _SYNOPSIS = _SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + '\n\n' | 55 _SYNOPSIS = _SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + '\n\n' |
| 63 | 56 |
| 64 _DESCRIPTION = (""" | 57 _DESCRIPTION = (""" |
| 65 Gets or sets the Cross-Origin Resource Sharing (CORS) configuration on one or | 58 Gets or sets the Cross-Origin Resource Sharing (CORS) configuration on one or |
| 66 more buckets. This command is supported for buckets only, not objects. A CORS | 59 more buckets. This command is supported for buckets only, not objects. An |
| 67 XML document should have the following structure: | 60 example CORS JSON document looks like the folllowing: |
| 68 | 61 |
| 69 <?xml version="1.0" ?> | 62 [ |
| 70 <CorsConfig> | 63 { |
| 71 <Cors> | 64 "origin": ["http://origin1.example.com"], |
| 72 <Origins> | 65 "responseHeader": ["Content-Type"], |
| 73 <Origin>http://origin1.example.com</Origin> | 66 "method": ["GET"], |
| 74 </Origins> | 67 "maxAgeSeconds": 3600 |
| 75 <Methods> | 68 } |
| 76 <Method>GET</Method> | 69 ] |
| 77 </Methods> | |
| 78 <ResponseHeaders> | |
| 79 <ResponseHeader>Content-Type</ResponseHeader> | |
| 80 </ResponseHeaders> | |
| 81 </Cors> | |
| 82 </CorsConfig> | |
| 83 | 70 |
| 84 The above XML document explicitly allows cross-origin GET requests from | 71 The above JSON document explicitly allows cross-origin GET requests from |
| 85 `http://origin1.example.com` and may include the Content-Type response header. | 72 http://origin1.example.com and may include the Content-Type response header. |
| 73 The preflight request may be cached for 1 hour. |
| 74 |
| 75 The following (empty) CORS JSON document removes all CORS configuration for |
| 76 a bucket: |
| 77 |
| 78 [] |
| 86 | 79 |
| 87 The cors command has two sub-commands: | 80 The cors command has two sub-commands: |
| 88 """ + '\n'.join([_GET_DESCRIPTION, _SET_DESCRIPTION]) + """ | 81 """ + '\n'.join([_GET_DESCRIPTION, _SET_DESCRIPTION]) + """ |
| 89 For more info about CORS, see http://www.w3.org/TR/cors/. | 82 For more info about CORS, see http://www.w3.org/TR/cors/. |
| 90 """) | 83 """) |
| 91 | 84 |
| 92 _detailed_help_text = CreateHelpText(_SYNOPSIS, _DESCRIPTION) | 85 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) |
| 93 | 86 |
| 94 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) | 87 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) |
| 95 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) | 88 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) |
| 96 | 89 |
| 90 |
| 97 class CorsCommand(Command): | 91 class CorsCommand(Command): |
| 98 """Implementation of gsutil cors command.""" | 92 """Implementation of gsutil cors command.""" |
| 99 | 93 |
| 100 # Command specification (processed by parent class). | 94 # Command specification. See base class for documentation. |
| 101 command_spec = { | 95 command_spec = Command.CreateCommandSpec( |
| 102 # Name of command. | 96 'cors', |
| 103 COMMAND_NAME : 'cors', | 97 command_name_aliases=['getcors', 'setcors'], |
| 104 # List of command name aliases. | 98 min_args=2, |
| 105 COMMAND_NAME_ALIASES : ['getcors', 'setcors'], | 99 max_args=NO_MAX, |
| 106 # Min number of args required by this command. | 100 supported_sub_args='', |
| 107 MIN_ARGS : 2, | 101 file_url_ok=False, |
| 108 # Max number of args required by this command, or NO_MAX. | 102 provider_url_ok=False, |
| 109 MAX_ARGS : NO_MAX, | 103 urls_start_arg=1, |
| 110 # Getopt-style string specifying acceptable sub args. | 104 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 111 SUPPORTED_SUB_ARGS : '', | 105 gs_default_api=ApiSelector.JSON, |
| 112 # True if file URIs acceptable for this command. | 106 ) |
| 113 FILE_URIS_OK : False, | 107 # Help specification. See help_provider.py for documentation. |
| 114 # True if provider-only URIs acceptable for this command. | 108 help_spec = Command.HelpSpec( |
| 115 PROVIDER_URIS_OK : False, | 109 help_name='cors', |
| 116 # Index in args of first URI arg. | 110 help_name_aliases=['getcors', 'setcors', 'cross-origin'], |
| 117 URIS_START_ARG : 1, | 111 help_type='command_help', |
| 118 } | 112 help_one_line_summary=( |
| 119 help_spec = { | 113 'Set a CORS JSON document for one or more buckets'), |
| 120 # Name of command or auxiliary help info for which this help applies. | 114 help_text=_DETAILED_HELP_TEXT, |
| 121 HELP_NAME : 'cors', | 115 subcommand_help_text={'get': _get_help_text, 'set': _set_help_text}, |
| 122 # List of help name aliases. | 116 ) |
| 123 HELP_NAME_ALIASES : ['getcors', 'setcors', 'cross-origin'], | |
| 124 # Type of help) | |
| 125 HELP_TYPE : HelpType.COMMAND_HELP, | |
| 126 # One line summary of this help. | |
| 127 HELP_ONE_LINE_SUMMARY : 'Set a CORS XML document for one or more buckets', | |
| 128 # The full help text. | |
| 129 HELP_TEXT : _detailed_help_text, | |
| 130 # Help text for sub-commands. | |
| 131 SUBCOMMAND_HELP_TEXT : {'get' : _get_help_text, | |
| 132 'set' : _set_help_text}, | |
| 133 } | |
| 134 | 117 |
| 135 def _CalculateUrisStartArg(self): | 118 def _CalculateUrlsStartArg(self): |
| 136 if not self.args: | 119 if not self.args: |
| 137 self._RaiseWrongNumberOfArgumentsException() | 120 self._RaiseWrongNumberOfArgumentsException() |
| 138 if (self.args[0].lower() == 'set'): | 121 if self.args[0].lower() == 'set': |
| 139 return 2 | 122 return 2 |
| 140 else: | 123 else: |
| 141 return 1 | 124 return 1 |
| 142 | 125 |
| 143 def _SetCors(self): | 126 def _SetCors(self): |
| 127 """Sets CORS configuration on a Google Cloud Storage bucket.""" |
| 144 cors_arg = self.args[0] | 128 cors_arg = self.args[0] |
| 145 uri_args = self.args[1:] | 129 url_args = self.args[1:] |
| 146 # Disallow multi-provider 'cors set' requests. | 130 # Disallow multi-provider 'cors set' requests. |
| 147 storage_uri = self.UrisAreForSingleProvider(uri_args) | 131 if not UrlsAreForSingleProvider(url_args): |
| 148 if not storage_uri: | |
| 149 raise CommandException('"%s" command spanning providers not allowed.' % | 132 raise CommandException('"%s" command spanning providers not allowed.' % |
| 150 self.command_name) | 133 self.command_name) |
| 151 | 134 |
| 152 # Open, read and parse file containing XML document. | 135 # Open, read and parse file containing JSON document. |
| 153 cors_file = open(cors_arg, 'r') | 136 cors_file = open(cors_arg, 'r') |
| 154 cors_txt = cors_file.read() | 137 cors_txt = cors_file.read() |
| 155 cors_file.close() | 138 cors_file.close() |
| 156 cors_obj = Cors() | |
| 157 | 139 |
| 158 # Parse XML document and convert into Cors object. | 140 self.api = self.gsutil_api.GetApiSelector( |
| 159 h = handler.XmlHandler(cors_obj, None) | 141 StorageUrlFromString(url_args[0]).scheme) |
| 160 try: | |
| 161 xml.sax.parseString(cors_txt, h) | |
| 162 except xml.sax._exceptions.SAXParseException, e: | |
| 163 raise CommandException('Requested CORS is invalid: %s at line %s, ' | |
| 164 'column %s' % (e.getMessage(), e.getLineNumber(), | |
| 165 e.getColumnNumber())) | |
| 166 | 142 |
| 167 # Iterate over URIs, expanding wildcards, and setting the CORS on each. | 143 cors = CorsTranslation.JsonCorsToMessageEntries(cors_txt) |
| 144 if not cors: |
| 145 cors = REMOVE_CORS_CONFIG |
| 146 |
| 147 # Iterate over URLs, expanding wildcards and setting the CORS on each. |
| 168 some_matched = False | 148 some_matched = False |
| 169 for uri_str in uri_args: | 149 for url_str in url_args: |
| 170 for blr in self.WildcardIterator(uri_str): | 150 bucket_iter = self.GetBucketUrlIterFromArg(url_str, bucket_fields=['id']) |
| 171 uri = blr.GetUri() | 151 for blr in bucket_iter: |
| 172 if not uri.names_bucket(): | 152 url = blr.storage_url |
| 173 raise CommandException('URI %s must name a bucket for the %s command' | |
| 174 % (str(uri), self.command_name)) | |
| 175 some_matched = True | 153 some_matched = True |
| 176 self.logger.info('Setting CORS on %s...', uri) | 154 self.logger.info('Setting CORS on %s...', blr) |
| 177 uri.set_cors(cors_obj, False, self.headers) | 155 if url.scheme == 's3': |
| 156 self.gsutil_api.XmlPassThroughSetCors( |
| 157 cors_txt, url, provider=url.scheme) |
| 158 else: |
| 159 bucket_metadata = apitools_messages.Bucket(cors=cors) |
| 160 self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata, |
| 161 provider=url.scheme, fields=['id']) |
| 178 if not some_matched: | 162 if not some_matched: |
| 179 raise CommandException('No URIs matched') | 163 raise CommandException('No URLs matched') |
| 164 return 0 |
| 180 | 165 |
| 181 def _GetCors(self): | 166 def _GetCors(self): |
| 182 # Wildcarding is allowed but must resolve to just one bucket. | 167 """Gets CORS configuration for a Google Cloud Storage bucket.""" |
| 183 uris = list(self.WildcardIterator(self.args[0]).IterUris()) | 168 bucket_url, bucket_metadata = self.GetSingleBucketUrlFromArg( |
| 184 if len(uris) == 0: | 169 self.args[0], bucket_fields=['cors']) |
| 185 raise CommandException('No URIs matched') | |
| 186 if len(uris) != 1: | |
| 187 raise CommandException('%s matched more than one URI, which is not\n' | |
| 188 'allowed by the %s command' % (self.args[0], self.command_name)) | |
| 189 uri = uris[0] | |
| 190 if not uri.names_bucket(): | |
| 191 raise CommandException('"%s" command must specify a bucket' % | |
| 192 self.command_name) | |
| 193 cors = uri.get_cors(False, self.headers) | |
| 194 # Pretty-print the XML to make it more easily human editable. | |
| 195 parsed_xml = xml.dom.minidom.parseString(cors.to_xml().encode('utf-8')) | |
| 196 sys.stdout.write(parsed_xml.toprettyxml(indent=' ')) | |
| 197 | 170 |
| 198 # Command entry point. | 171 if bucket_url.scheme == 's3': |
| 172 sys.stdout.write(self.gsutil_api.XmlPassThroughGetCors( |
| 173 bucket_url, provider=bucket_url.scheme)) |
| 174 else: |
| 175 if bucket_metadata.cors: |
| 176 sys.stdout.write( |
| 177 CorsTranslation.MessageEntriesToJson(bucket_metadata.cors)) |
| 178 else: |
| 179 sys.stdout.write('%s has no CORS configuration.\n' % bucket_url) |
| 180 return 0 |
| 181 |
| 199 def RunCommand(self): | 182 def RunCommand(self): |
| 183 """Command entry point for the cors command.""" |
| 200 action_subcommand = self.args.pop(0) | 184 action_subcommand = self.args.pop(0) |
| 201 self.CheckArguments() | |
| 202 if action_subcommand == 'get': | 185 if action_subcommand == 'get': |
| 203 func = self._GetCors | 186 func = self._GetCors |
| 204 elif action_subcommand == 'set': | 187 elif action_subcommand == 'set': |
| 205 func = self._SetCors | 188 func = self._SetCors |
| 206 else: | 189 else: |
| 207 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' | 190 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' |
| 208 'See "gsutil help cors".') % | 191 'See "gsutil help cors".') % |
| 209 (action_subcommand, self.command_name)) | 192 (action_subcommand, self.command_name)) |
| 210 func() | 193 return func() |
| 211 return 0 | |
| OLD | NEW |