thirdparty/google_appengine/google/appengine/api/appinfo.py
changeset 2864 2e0b0af889be
parent 2273 e4cb9c53db3e
child 3031 7678f72140e6
equal deleted inserted replaced
2862:27971a13089f 2864:2e0b0af889be
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14 # See the License for the specific language governing permissions and
    14 # See the License for the specific language governing permissions and
    15 # limitations under the License.
    15 # limitations under the License.
    16 #
    16 #
    17 
    17 
    18 """AppInfo tools
    18 """AppInfo tools.
    19 
    19 
    20 Library for working with AppInfo records in memory, store and load from
    20 Library for working with AppInfo records in memory, store and load from
    21 configuration files.
    21 configuration files.
    22 """
    22 """
    23 
    23 
    27 
    27 
    28 import re
    28 import re
    29 
    29 
    30 from google.appengine.api import appinfo_errors
    30 from google.appengine.api import appinfo_errors
    31 from google.appengine.api import validation
    31 from google.appengine.api import validation
       
    32 from google.appengine.api import yaml_builder
    32 from google.appengine.api import yaml_listener
    33 from google.appengine.api import yaml_listener
    33 from google.appengine.api import yaml_builder
       
    34 from google.appengine.api import yaml_object
    34 from google.appengine.api import yaml_object
    35 
    35 
    36 
    36 
    37 _URL_REGEX = r'(?!\^)/|\.|(\(.).*(?!\$).'
    37 _URL_REGEX = r'(?!\^)/|\.|(\(.).*(?!\$).'
    38 _FILES_REGEX = r'(?!\^).*(?!\$).'
    38 _FILES_REGEX = r'(?!\^).*(?!\$).'
    39 
    39 
    40 _DELTA_REGEX = r'([1-9][0-9]*)([DdHhMm]|[sS]?)'
    40 _DELTA_REGEX = r'([1-9][0-9]*)([DdHhMm]|[sS]?)'
    41 _EXPIRATION_REGEX = r'\s*(%s)(\s+%s)*\s*' % (_DELTA_REGEX, _DELTA_REGEX)
    41 _EXPIRATION_REGEX = r'\s*(%s)(\s+%s)*\s*' % (_DELTA_REGEX, _DELTA_REGEX)
    42 
    42 
       
    43 _SERVICE_RE_STRING = r'(mail|xmpp_message)'
       
    44 
    43 _EXPIRATION_CONVERSIONS = {
    45 _EXPIRATION_CONVERSIONS = {
    44   'd': 60 * 60 * 24,
    46     'd': 60 * 60 * 24,
    45   'h': 60 * 60,
    47     'h': 60 * 60,
    46   'm': 60,
    48     'm': 60,
    47   's': 1,
    49     's': 1,
    48 }
    50 }
    49 
    51 
    50 APP_ID_MAX_LEN = 100
    52 APP_ID_MAX_LEN = 100
    51 MAJOR_VERSION_ID_MAX_LEN = 100
    53 MAJOR_VERSION_ID_MAX_LEN = 100
    52 MAX_URL_MAPS = 100
    54 MAX_URL_MAPS = 100
    70 SECURE_HTTPS = 'always'
    72 SECURE_HTTPS = 'always'
    71 SECURE_HTTP_OR_HTTPS = 'optional'
    73 SECURE_HTTP_OR_HTTPS = 'optional'
    72 
    74 
    73 REQUIRE_MATCHING_FILE = 'require_matching_file'
    75 REQUIRE_MATCHING_FILE = 'require_matching_file'
    74 
    76 
    75 DEFAULT_SKIP_FILES = (r"^(.*/)?("
    77 DEFAULT_SKIP_FILES = (r'^(.*/)?('
    76                       r"(app\.yaml)|"
    78                       r'(app\.yaml)|'
    77                       r"(app\.yml)|"
    79                       r'(app\.yml)|'
    78                       r"(index\.yaml)|"
    80                       r'(index\.yaml)|'
    79                       r"(index\.yml)|"
    81                       r'(index\.yml)|'
    80                       r"(#.*#)|"
    82                       r'(#.*#)|'
    81                       r"(.*~)|"
    83                       r'(.*~)|'
    82                       r"(.*\.py[co])|"
    84                       r'(.*\.py[co])|'
    83                       r"(.*/RCS/.*)|"
    85                       r'(.*/RCS/.*)|'
    84                       r"(\..*)|"
    86                       r'(\..*)|'
    85                       r")$")
    87                       r')$')
    86 
    88 
    87 LOGIN = 'login'
    89 LOGIN = 'login'
    88 SECURE = 'secure'
    90 SECURE = 'secure'
    89 URL = 'url'
    91 URL = 'url'
    90 STATIC_FILES = 'static_files'
    92 STATIC_FILES = 'static_files'
    99 RUNTIME = 'runtime'
   101 RUNTIME = 'runtime'
   100 API_VERSION = 'api_version'
   102 API_VERSION = 'api_version'
   101 HANDLERS = 'handlers'
   103 HANDLERS = 'handlers'
   102 DEFAULT_EXPIRATION = 'default_expiration'
   104 DEFAULT_EXPIRATION = 'default_expiration'
   103 SKIP_FILES = 'skip_files'
   105 SKIP_FILES = 'skip_files'
       
   106 SERVICES = 'inbound_services'
   104 
   107 
   105 
   108 
   106 class URLMap(validation.Validated):
   109 class URLMap(validation.Validated):
   107   """Mapping from URLs to handlers.
   110   """Mapping from URLs to handlers.
   108 
   111 
   174       upload: images/(.*)
   177       upload: images/(.*)
   175   """
   178   """
   176 
   179 
   177   ATTRIBUTES = {
   180   ATTRIBUTES = {
   178 
   181 
   179     URL: validation.Optional(_URL_REGEX),
   182       URL: validation.Optional(_URL_REGEX),
   180     LOGIN: validation.Options(LOGIN_OPTIONAL,
   183       LOGIN: validation.Options(LOGIN_OPTIONAL,
   181                               LOGIN_REQUIRED,
   184                                 LOGIN_REQUIRED,
   182                               LOGIN_ADMIN,
   185                                 LOGIN_ADMIN,
   183                               default=LOGIN_OPTIONAL),
   186                                 default=LOGIN_OPTIONAL),
   184 
   187 
   185     SECURE: validation.Options(SECURE_HTTP,
   188       SECURE: validation.Options(SECURE_HTTP,
   186                                SECURE_HTTPS,
   189                                  SECURE_HTTPS,
   187                                SECURE_HTTP_OR_HTTPS,
   190                                  SECURE_HTTP_OR_HTTPS,
   188                                default=SECURE_HTTP),
   191                                  default=SECURE_HTTP),
   189 
   192 
   190 
   193 
   191 
   194 
   192     HANDLER_STATIC_FILES: validation.Optional(_FILES_REGEX),
   195       HANDLER_STATIC_FILES: validation.Optional(_FILES_REGEX),
   193     UPLOAD: validation.Optional(_FILES_REGEX),
   196       UPLOAD: validation.Optional(_FILES_REGEX),
   194 
   197 
   195 
   198 
   196     HANDLER_STATIC_DIR: validation.Optional(_FILES_REGEX),
   199       HANDLER_STATIC_DIR: validation.Optional(_FILES_REGEX),
   197 
   200 
   198 
   201 
   199     MIME_TYPE: validation.Optional(str),
   202       MIME_TYPE: validation.Optional(str),
   200     EXPIRATION: validation.Optional(_EXPIRATION_REGEX),
   203       EXPIRATION: validation.Optional(_EXPIRATION_REGEX),
   201 
   204 
   202 
   205 
   203     HANDLER_SCRIPT: validation.Optional(_FILES_REGEX),
   206       HANDLER_SCRIPT: validation.Optional(_FILES_REGEX),
   204 
   207 
   205     REQUIRE_MATCHING_FILE: validation.Optional(bool),
   208       REQUIRE_MATCHING_FILE: validation.Optional(bool),
   206   }
   209   }
   207 
   210 
   208   COMMON_FIELDS = set([URL, LOGIN, SECURE])
   211   COMMON_FIELDS = set([URL, LOGIN, SECURE])
   209 
   212 
   210   ALLOWED_FIELDS = {
   213   ALLOWED_FIELDS = {
   211     HANDLER_STATIC_FILES: (MIME_TYPE, UPLOAD, EXPIRATION,
   214       HANDLER_STATIC_FILES: (MIME_TYPE, UPLOAD, EXPIRATION,
   212                            REQUIRE_MATCHING_FILE),
   215                              REQUIRE_MATCHING_FILE),
   213     HANDLER_STATIC_DIR: (MIME_TYPE, EXPIRATION, REQUIRE_MATCHING_FILE),
   216       HANDLER_STATIC_DIR: (MIME_TYPE, EXPIRATION, REQUIRE_MATCHING_FILE),
   214     HANDLER_SCRIPT: (),
   217       HANDLER_SCRIPT: (),
   215   }
   218   }
   216 
   219 
   217   def GetHandler(self):
   220   def GetHandler(self):
   218     """Get handler for mapping.
   221     """Get handler for mapping.
   219 
   222 
   251     for attribute in self.ATTRIBUTES.iterkeys():
   254     for attribute in self.ATTRIBUTES.iterkeys():
   252       if (getattr(self, attribute) is not None and
   255       if (getattr(self, attribute) is not None and
   253           not (attribute in allowed_fields or
   256           not (attribute in allowed_fields or
   254                attribute in URLMap.COMMON_FIELDS or
   257                attribute in URLMap.COMMON_FIELDS or
   255                attribute == mapping_type)):
   258                attribute == mapping_type)):
   256             raise appinfo_errors.UnexpectedHandlerAttribute(
   259         raise appinfo_errors.UnexpectedHandlerAttribute(
   257                 'Unexpected attribute "%s" for mapping type %s.' %
   260             'Unexpected attribute "%s" for mapping type %s.' %
   258                 (attribute, mapping_type))
   261             (attribute, mapping_type))
   259 
   262 
   260     if mapping_type == HANDLER_STATIC_FILES and not self.upload:
   263     if mapping_type == HANDLER_STATIC_FILES and not self.upload:
   261       raise appinfo_errors.MissingHandlerAttribute(
   264       raise appinfo_errors.MissingHandlerAttribute(
   262           'Missing "%s" attribute for URL "%s".' % (UPLOAD, self.url))
   265           'Missing "%s" attribute for URL "%s".' % (UPLOAD, self.url))
   263 
   266 
   307   """
   310   """
   308 
   311 
   309   ATTRIBUTES = {
   312   ATTRIBUTES = {
   310 
   313 
   311 
   314 
   312     APPLICATION: APPLICATION_RE_STRING,
   315       APPLICATION: APPLICATION_RE_STRING,
   313     VERSION: VERSION_RE_STRING,
   316       VERSION: VERSION_RE_STRING,
   314     RUNTIME: RUNTIME_RE_STRING,
   317       RUNTIME: RUNTIME_RE_STRING,
   315 
   318 
   316 
   319 
   317     API_VERSION: API_VERSION_RE_STRING,
   320       API_VERSION: API_VERSION_RE_STRING,
   318     HANDLERS: validation.Optional(validation.Repeated(URLMap)),
   321       HANDLERS: validation.Optional(validation.Repeated(URLMap)),
   319     DEFAULT_EXPIRATION: validation.Optional(_EXPIRATION_REGEX),
   322 
   320     SKIP_FILES: validation.RegexStr(default=DEFAULT_SKIP_FILES)
   323       SERVICES: validation.Optional(validation.Repeated(
       
   324           validation.Regex(_SERVICE_RE_STRING))),
       
   325       DEFAULT_EXPIRATION: validation.Optional(_EXPIRATION_REGEX),
       
   326       SKIP_FILES: validation.RegexStr(default=DEFAULT_SKIP_FILES)
   321   }
   327   }
   322 
   328 
   323   def CheckInitialized(self):
   329   def CheckInitialized(self):
   324     """Ensures that at least one url mapping is provided.
   330     """Ensures that at least one url mapping is provided.
   325 
   331 
   347 
   353 
   348   Returns:
   354   Returns:
   349     An instance of AppInfoExternal as loaded from a YAML file.
   355     An instance of AppInfoExternal as loaded from a YAML file.
   350 
   356 
   351   Raises:
   357   Raises:
   352     EmptyConfigurationFile when there are no documents in YAML file.
   358     ValueError: if a specified service is not valid.
   353     MultipleConfigurationFile when there is more than one document in YAML
   359     EmptyConfigurationFile: when there are no documents in YAML file.
       
   360     MultipleConfigurationFile: when there is more than one document in YAML
   354     file.
   361     file.
   355   """
   362   """
   356   builder = yaml_object.ObjectBuilder(AppInfoExternal)
   363   builder = yaml_object.ObjectBuilder(AppInfoExternal)
   357   handler = yaml_builder.BuilderHandler(builder)
   364   handler = yaml_builder.BuilderHandler(builder)
   358   listener = yaml_listener.EventListener(handler)
   365   listener = yaml_listener.EventListener(handler)
   384 
   391 
   385 
   392 
   386 
   393 
   387 _file_path_positive_re = re.compile(r'^[ 0-9a-zA-Z\._\+/\$-]{1,256}$')
   394 _file_path_positive_re = re.compile(r'^[ 0-9a-zA-Z\._\+/\$-]{1,256}$')
   388 
   395 
   389 _file_path_negative_1_re = re.compile(r'\.\.|^\./|\.$|/\./|^-')
   396 _file_path_negative_1_re = re.compile(r'\.\.|^\./|\.$|/\./|^-|^_ah/')
   390 
   397 
   391 _file_path_negative_2_re = re.compile(r'//|/$')
   398 _file_path_negative_2_re = re.compile(r'//|/$')
   392 
   399 
   393 _file_path_negative_3_re = re.compile(r'^ | $|/ | /')
   400 _file_path_negative_3_re = re.compile(r'^ | $|/ | /')
   394 
   401 
   411     is valid.
   418     is valid.
   412   """
   419   """
   413   if _file_path_positive_re.match(filename) is None:
   420   if _file_path_positive_re.match(filename) is None:
   414     return 'Invalid character in filename: %s' % filename
   421     return 'Invalid character in filename: %s' % filename
   415   if _file_path_negative_1_re.search(filename) is not None:
   422   if _file_path_negative_1_re.search(filename) is not None:
   416     return ('Filename cannot contain "." or ".." or start with "-": %s' %
   423     return ('Filename cannot contain "." or ".." '
       
   424             'or start with "-" or "_ah/": %s' %
   417             filename)
   425             filename)
   418   if _file_path_negative_2_re.search(filename) is not None:
   426   if _file_path_negative_2_re.search(filename) is not None:
   419     return 'Filename cannot have trailing / or contain //: %s' % filename
   427     return 'Filename cannot have trailing / or contain //: %s' % filename
   420   if _file_path_negative_3_re.search(filename) is not None:
   428   if _file_path_negative_3_re.search(filename) is not None:
   421     return 'Any spaces must be in the middle of a filename: %s' % filename
   429     return 'Any spaces must be in the middle of a filename: %s' % filename