Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | import re |
michael@0 | 6 | import codecs |
michael@0 | 7 | |
michael@0 | 8 | class MalformedLocaleFileError(Exception): |
michael@0 | 9 | pass |
michael@0 | 10 | |
michael@0 | 11 | def parse_file(path): |
michael@0 | 12 | return parse(read_file(path), path) |
michael@0 | 13 | |
michael@0 | 14 | def read_file(path): |
michael@0 | 15 | try: |
michael@0 | 16 | return codecs.open( path, "r", "utf-8" ).readlines() |
michael@0 | 17 | except UnicodeDecodeError, e: |
michael@0 | 18 | raise MalformedLocaleFileError( |
michael@0 | 19 | 'Following locale file is not a valid ' + |
michael@0 | 20 | 'UTF-8 file: %s\n%s"' % (path, str(e))) |
michael@0 | 21 | |
michael@0 | 22 | COMMENT = re.compile(r'\s*#') |
michael@0 | 23 | EMPTY = re.compile(r'^\s+$') |
michael@0 | 24 | KEYVALUE = re.compile(r"\s*([^=:]+)(=|:)\s*(.*)") |
michael@0 | 25 | |
michael@0 | 26 | def parse(lines, path=None): |
michael@0 | 27 | lines = iter(lines) |
michael@0 | 28 | lineNo = 1 |
michael@0 | 29 | pairs = dict() |
michael@0 | 30 | for line in lines: |
michael@0 | 31 | if COMMENT.match(line) or EMPTY.match(line) or len(line) == 0: |
michael@0 | 32 | continue |
michael@0 | 33 | m = KEYVALUE.match(line) |
michael@0 | 34 | if not m: |
michael@0 | 35 | raise MalformedLocaleFileError( |
michael@0 | 36 | 'Following locale file is not a valid .properties file: %s\n' |
michael@0 | 37 | 'Line %d is incorrect:\n%s' % (path, lineNo, line)) |
michael@0 | 38 | |
michael@0 | 39 | # All spaces are strip. Spaces at the beginning are stripped |
michael@0 | 40 | # by the regular expression. We have to strip spaces at the end. |
michael@0 | 41 | key = m.group(1).rstrip() |
michael@0 | 42 | val = m.group(3).rstrip() |
michael@0 | 43 | val = val.encode('raw-unicode-escape').decode('raw-unicode-escape') |
michael@0 | 44 | |
michael@0 | 45 | # `key` can be empty when key is only made of spaces |
michael@0 | 46 | if not key: |
michael@0 | 47 | raise MalformedLocaleFileError( |
michael@0 | 48 | 'Following locale file is not a valid .properties file: %s\n' |
michael@0 | 49 | 'Key is invalid on line %d is incorrect:\n%s' % |
michael@0 | 50 | (path, lineNo, line)) |
michael@0 | 51 | |
michael@0 | 52 | # Multiline value: keep reading lines, while lines end with backslash |
michael@0 | 53 | # and strip spaces at the beginning of lines except the last line |
michael@0 | 54 | # that doesn't end up with backslash, we strip all spaces for this one. |
michael@0 | 55 | if val.endswith("\\"): |
michael@0 | 56 | val = val[:-1] |
michael@0 | 57 | try: |
michael@0 | 58 | # remove spaces before/after and especially the \n at EOL |
michael@0 | 59 | line = lines.next().strip() |
michael@0 | 60 | while line.endswith("\\"): |
michael@0 | 61 | val += line[:-1].lstrip() |
michael@0 | 62 | line = lines.next() |
michael@0 | 63 | lineNo += 1 |
michael@0 | 64 | val += line.strip() |
michael@0 | 65 | except StopIteration: |
michael@0 | 66 | raise MalformedLocaleFileError( |
michael@0 | 67 | 'Following locale file is not a valid .properties file: %s\n' |
michael@0 | 68 | 'Unexpected EOF in multiline sequence at line %d:\n%s' % |
michael@0 | 69 | (path, lineNo, line)) |
michael@0 | 70 | # Save this new pair |
michael@0 | 71 | pairs[key] = val |
michael@0 | 72 | lineNo += 1 |
michael@0 | 73 | |
michael@0 | 74 | normalize_plural(path, pairs) |
michael@0 | 75 | return pairs |
michael@0 | 76 | |
michael@0 | 77 | # Plural forms in properties files are defined like this: |
michael@0 | 78 | # key = other form |
michael@0 | 79 | # key[one] = one form |
michael@0 | 80 | # key[...] = ... |
michael@0 | 81 | # Parse them and merge each key into one object containing all forms: |
michael@0 | 82 | # key: { |
michael@0 | 83 | # other: "other form", |
michael@0 | 84 | # one: "one form", |
michael@0 | 85 | # ...: ... |
michael@0 | 86 | # } |
michael@0 | 87 | PLURAL_FORM = re.compile(r'^(.*)\[(zero|one|two|few|many|other)\]$') |
michael@0 | 88 | def normalize_plural(path, pairs): |
michael@0 | 89 | for key in list(pairs.keys()): |
michael@0 | 90 | m = PLURAL_FORM.match(key) |
michael@0 | 91 | if not m: |
michael@0 | 92 | continue |
michael@0 | 93 | main_key = m.group(1) |
michael@0 | 94 | plural_form = m.group(2) |
michael@0 | 95 | # Allows not specifying a generic key (i.e a key without [form]) |
michael@0 | 96 | if not main_key in pairs: |
michael@0 | 97 | pairs[main_key] = {} |
michael@0 | 98 | # Ensure that we always have the [other] form |
michael@0 | 99 | if not main_key + "[other]" in pairs: |
michael@0 | 100 | raise MalformedLocaleFileError( |
michael@0 | 101 | 'Following locale file is not a valid UTF-8 file: %s\n' |
michael@0 | 102 | 'This plural form doesn\'t have a matching `%s[other]` form:\n' |
michael@0 | 103 | '%s\n' |
michael@0 | 104 | 'You have to defined following key:\n%s' |
michael@0 | 105 | % (path, main_key, key, main_key)) |
michael@0 | 106 | # convert generic form into an object if it is still a string |
michael@0 | 107 | if isinstance(pairs[main_key], unicode): |
michael@0 | 108 | pairs[main_key] = {"other": pairs[main_key]} |
michael@0 | 109 | # then, add this new plural form |
michael@0 | 110 | pairs[main_key][plural_form] = pairs[key] |
michael@0 | 111 | del pairs[key] |