OLD | NEW |
1 # Copyright 2016 The LUCI Authors. All rights reserved. | 1 # Copyright 2016 The LUCI Authors. All rights reserved. |
2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
4 | 4 |
5 import re | 5 import re |
| 6 import string |
6 import types | 7 import types |
7 | 8 |
| 9 _ALNUM_CHARS = string.ascii_letters + string.digits |
8 _SEGMENT_RE_BASE = r'[a-zA-Z0-9][a-zA-Z0-9:_\-.]*' | 10 _SEGMENT_RE_BASE = r'[a-zA-Z0-9][a-zA-Z0-9:_\-.]*' |
9 _STREAM_NAME_RE = re.compile('^(' + _SEGMENT_RE_BASE + ')(/' + | 11 _STREAM_NAME_RE = re.compile('^(' + _SEGMENT_RE_BASE + ')(/' + |
10 _SEGMENT_RE_BASE + ')*$') | 12 _SEGMENT_RE_BASE + ')*$') |
11 _MAX_STREAM_NAME_LENGTH = 4096 | 13 _MAX_STREAM_NAME_LENGTH = 4096 |
12 | 14 |
13 _MAX_TAG_KEY_LENGTH = 64 | 15 _MAX_TAG_KEY_LENGTH = 64 |
14 _MAX_TAG_VALUE_LENGTH = 4096 | 16 _MAX_TAG_VALUE_LENGTH = 4096 |
15 | 17 |
| 18 |
16 def validate_stream_name(v, maxlen=None): | 19 def validate_stream_name(v, maxlen=None): |
17 """Verifies that a given stream name is valid. | 20 """Verifies that a given stream name is valid. |
18 | 21 |
19 Args: | 22 Args: |
20 v (str): The stream name string. | 23 v (str): The stream name string. |
21 | 24 |
22 | 25 |
23 Raises: | 26 Raises: |
24 ValueError if the stream name is invalid. | 27 ValueError if the stream name is invalid. |
25 """ | 28 """ |
26 maxlen = maxlen or _MAX_STREAM_NAME_LENGTH | 29 maxlen = maxlen or _MAX_STREAM_NAME_LENGTH |
27 if len(v) > maxlen: | 30 if len(v) > maxlen: |
28 raise ValueError('Maximum length exceeded (%d > %d)' % (len(v), maxlen)) | 31 raise ValueError('Maximum length exceeded (%d > %d)' % (len(v), maxlen)) |
29 if _STREAM_NAME_RE.match(v) is None: | 32 if _STREAM_NAME_RE.match(v) is None: |
30 raise ValueError('Invalid stream name') | 33 raise ValueError('Invalid stream name') |
31 | 34 |
32 | 35 |
33 def validate_tag(key, value): | 36 def validate_tag(key, value): |
34 """Verifies that a given tag key/value is valid. | 37 """Verifies that a given tag key/value is valid. |
35 | 38 |
36 Args: | 39 Args: |
37 k (str): The tag key. | 40 k (str): The tag key. |
38 v (str): The tag value. | 41 v (str): The tag value. |
39 | 42 |
40 Raises: | 43 Raises: |
41 ValueError if the tag is not valid. | 44 ValueError if the tag is not valid. |
42 """ | 45 """ |
43 validate_stream_name(key, maxlen=_MAX_TAG_KEY_LENGTH) | 46 validate_stream_name(key, maxlen=_MAX_TAG_KEY_LENGTH) |
44 validate_stream_name(value, maxlen=_MAX_TAG_VALUE_LENGTH) | 47 validate_stream_name(value, maxlen=_MAX_TAG_VALUE_LENGTH) |
| 48 |
| 49 |
| 50 def normalize(v, prefix=None): |
| 51 """Given a string, "v", mutate it into a valid stream name. |
| 52 |
| 53 This operates by replacing invalid stream naem characters with underscores (_) |
| 54 when encountered. |
| 55 |
| 56 A special case is when "v" begins with an invalid character. In this case, we |
| 57 will replace it with the "prefix", if one is supplied. |
| 58 |
| 59 See _STREAM_NAME_RE for a description of a valid stream name. |
| 60 |
| 61 Raises: |
| 62 ValueError: If normalization could not be successfully performed. |
| 63 """ |
| 64 if len(v) == 0: |
| 65 if not prefix: |
| 66 raise ValueError('Cannot normalize empty name with no prefix.') |
| 67 v = prefix |
| 68 else: |
| 69 out = [] |
| 70 for i, ch in enumerate(v): |
| 71 if i == 0 and not _is_valid_stream_char(ch, first=True): |
| 72 # The first letter is special, and must be alphanumeric. |
| 73 # If we have a prefix, prepend that to the resulting string. |
| 74 if prefix is None: |
| 75 raise ValueError('Name has invalid beginning, and no prefix was ' |
| 76 'provided.') |
| 77 out.append(prefix) |
| 78 |
| 79 if not _is_valid_stream_char(ch): |
| 80 ch = '_' |
| 81 out.append(ch) |
| 82 v = ''.join(out) |
| 83 |
| 84 # Validate the resulting string. |
| 85 validate_stream_name(v) |
| 86 return v |
| 87 |
| 88 |
| 89 def _is_valid_stream_char(ch, first=False): |
| 90 """Returns (bool): True if a character is alphanumeric. |
| 91 |
| 92 The first character must be alphanumeric, matching [a-zA-Z0-9]. |
| 93 Additional characters must either be alphanumeric or one of: (: _ - .). |
| 94 |
| 95 Args: |
| 96 ch (str): the character to evaluate. |
| 97 first (bool): if true, apply special first-character constraints. |
| 98 """ |
| 99 # Alphanumeric check. |
| 100 if ch in _ALNUM_CHARS: |
| 101 return True |
| 102 if first: |
| 103 # The first character must be alphanumeric. |
| 104 return False |
| 105 |
| 106 # Check additional middle-name characters: |
| 107 return ch in ':_-./' |
OLD | NEW |