OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """An implementation of the server side of the Chromium sync protocol. | 5 """An implementation of the server side of the Chromium sync protocol. |
6 | 6 |
7 The details of the protocol are described mostly by comments in the protocol | 7 The details of the protocol are described mostly by comments in the protocol |
8 buffer definition at chrome/browser/sync/protocol/sync.proto. | 8 buffer definition at chrome/browser/sync/protocol/sync.proto. |
9 """ | 9 """ |
10 | 10 |
11 import cgi | 11 import cgi |
12 import copy | 12 import copy |
13 import operator | 13 import operator |
14 import pickle | 14 import pickle |
15 import random | 15 import random |
16 import string | |
16 import sys | 17 import sys |
17 import threading | 18 import threading |
18 import time | 19 import time |
19 import urlparse | 20 import urlparse |
20 | 21 |
21 import app_notification_specifics_pb2 | 22 import app_notification_specifics_pb2 |
22 import app_setting_specifics_pb2 | 23 import app_setting_specifics_pb2 |
23 import app_specifics_pb2 | 24 import app_specifics_pb2 |
24 import autofill_specifics_pb2 | 25 import autofill_specifics_pb2 |
25 import bookmark_specifics_pb2 | 26 import bookmark_specifics_pb2 |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
90 TYPED_URL: SYNC_TYPE_FIELDS['typed_url'], | 91 TYPED_URL: SYNC_TYPE_FIELDS['typed_url'], |
91 } | 92 } |
92 | 93 |
93 # The parent ID used to indicate a top-level node. | 94 # The parent ID used to indicate a top-level node. |
94 ROOT_ID = '0' | 95 ROOT_ID = '0' |
95 | 96 |
96 # Unix time epoch in struct_time format. The tuple corresponds to UTC Wednesday | 97 # Unix time epoch in struct_time format. The tuple corresponds to UTC Wednesday |
97 # Jan 1 1970, 00:00:00, non-dst. | 98 # Jan 1 1970, 00:00:00, non-dst. |
98 UNIX_TIME_EPOCH = (1970, 1, 1, 0, 0, 0, 3, 1, 0) | 99 UNIX_TIME_EPOCH = (1970, 1, 1, 0, 0, 0, 3, 1, 0) |
99 | 100 |
101 # The number of characters in the server-generated encryption key. | |
102 KEY_LENGTH = 16 | |
tim (not reviewing)
2012/07/19 21:42:03
nit: ENCRYPTION_KEY_LENGTH or KEYSTORE_ is a bit l
Nicolas Zea
2012/07/24 22:51:24
Done.
| |
103 | |
100 class Error(Exception): | 104 class Error(Exception): |
101 """Error class for this module.""" | 105 """Error class for this module.""" |
102 | 106 |
103 | 107 |
104 class ProtobufDataTypeFieldNotUnique(Error): | 108 class ProtobufDataTypeFieldNotUnique(Error): |
105 """An entry should not have more than one data type present.""" | 109 """An entry should not have more than one data type present.""" |
106 | 110 |
107 | 111 |
108 class DataTypeIdNotRecognized(Error): | 112 class DataTypeIdNotRecognized(Error): |
109 """The requested data type is not recognized.""" | 113 """The requested data type is not recognized.""" |
(...skipping 351 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
461 self._entries = {} | 465 self._entries = {} |
462 | 466 |
463 self.ResetStoreBirthday() | 467 self.ResetStoreBirthday() |
464 | 468 |
465 self.migration_history = MigrationHistory() | 469 self.migration_history = MigrationHistory() |
466 | 470 |
467 self.induced_error = sync_pb2.ClientToServerResponse.Error() | 471 self.induced_error = sync_pb2.ClientToServerResponse.Error() |
468 self.induced_error_frequency = 0 | 472 self.induced_error_frequency = 0 |
469 self.sync_count_before_errors = 0 | 473 self.sync_count_before_errors = 0 |
470 | 474 |
475 self._key = ''.join(random.choice( | |
476 string.ascii_uppercase + string.digits) for x in range(KEY_LENGTH)) | |
477 | |
471 def _SaveEntry(self, entry): | 478 def _SaveEntry(self, entry): |
472 """Insert or update an entry in the change log, and give it a new version. | 479 """Insert or update an entry in the change log, and give it a new version. |
473 | 480 |
474 The ID fields of this entry are assumed to be valid server IDs. This | 481 The ID fields of this entry are assumed to be valid server IDs. This |
475 entry will be updated with a new version number and sync_timestamp. | 482 entry will be updated with a new version number and sync_timestamp. |
476 | 483 |
477 Args: | 484 Args: |
478 entry: The entry to be added or updated. | 485 entry: The entry to be added or updated. |
479 """ | 486 """ |
480 self._version += 1 | 487 self._version += 1 |
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
657 | 664 |
658 # Restrict batch to requested types. Tombstones are untyped | 665 # Restrict batch to requested types. Tombstones are untyped |
659 # and will always get included. | 666 # and will always get included. |
660 filtered = [copy.deepcopy(item) for item in batch | 667 filtered = [copy.deepcopy(item) for item in batch |
661 if item.deleted or sieve.ClientWantsItem(item)] | 668 if item.deleted or sieve.ClientWantsItem(item)] |
662 | 669 |
663 # The new client timestamp is the timestamp of the last item in the | 670 # The new client timestamp is the timestamp of the last item in the |
664 # batch, even if that item was filtered out. | 671 # batch, even if that item was filtered out. |
665 return (batch[-1].version, filtered, len(new_changes) - len(batch)) | 672 return (batch[-1].version, filtered, len(new_changes) - len(batch)) |
666 | 673 |
674 def GetKey(self): | |
675 """Returns the encryption key for this account.""" | |
676 return self._key | |
677 | |
667 def _CopyOverImmutableFields(self, entry): | 678 def _CopyOverImmutableFields(self, entry): |
668 """Preserve immutable fields by copying pre-commit state. | 679 """Preserve immutable fields by copying pre-commit state. |
669 | 680 |
670 Args: | 681 Args: |
671 entry: A sync entity from the client. | 682 entry: A sync entity from the client. |
672 """ | 683 """ |
673 if entry.id_string in self._entries: | 684 if entry.id_string in self._entries: |
674 if self._entries[entry.id_string].HasField( | 685 if self._entries[entry.id_string].HasField( |
675 'server_defined_unique_tag'): | 686 'server_defined_unique_tag'): |
676 entry.server_defined_unique_tag = ( | 687 entry.server_defined_unique_tag = ( |
(...skipping 359 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1036 response = 'Could not interpret datatype name' | 1047 response = 'Could not interpret datatype name' |
1037 code = 400 | 1048 code = 400 |
1038 finally: | 1049 finally: |
1039 self.account_lock.release() | 1050 self.account_lock.release() |
1040 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % | 1051 return (code, '<html><title>Migration: %d</title><H1>%d %s</H1></html>' % |
1041 (code, code, response)) | 1052 (code, code, response)) |
1042 | 1053 |
1043 def HandleSetInducedError(self, path): | 1054 def HandleSetInducedError(self, path): |
1044 query = urlparse.urlparse(path)[4] | 1055 query = urlparse.urlparse(path)[4] |
1045 self.account_lock.acquire() | 1056 self.account_lock.acquire() |
1046 code = 200; | 1057 code = 200 |
1047 response = 'Success' | 1058 response = 'Success' |
1048 error = sync_pb2.ClientToServerResponse.Error() | 1059 error = sync_pb2.ClientToServerResponse.Error() |
1049 try: | 1060 try: |
1050 error_type = urlparse.parse_qs(query)['error'] | 1061 error_type = urlparse.parse_qs(query)['error'] |
1051 action = urlparse.parse_qs(query)['action'] | 1062 action = urlparse.parse_qs(query)['action'] |
1052 error.error_type = int(error_type[0]) | 1063 error.error_type = int(error_type[0]) |
1053 error.action = int(action[0]) | 1064 error.action = int(action[0]) |
1054 try: | 1065 try: |
1055 error.url = (urlparse.parse_qs(query)['url'])[0] | 1066 error.url = (urlparse.parse_qs(query)['url'])[0] |
1056 except KeyError: | 1067 except KeyError: |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1125 | 1136 |
1126 try: | 1137 try: |
1127 request = sync_pb2.ClientToServerMessage() | 1138 request = sync_pb2.ClientToServerMessage() |
1128 request.MergeFromString(raw_request) | 1139 request.MergeFromString(raw_request) |
1129 contents = request.message_contents | 1140 contents = request.message_contents |
1130 | 1141 |
1131 response = sync_pb2.ClientToServerResponse() | 1142 response = sync_pb2.ClientToServerResponse() |
1132 response.error_code = sync_enums_pb2.SyncEnums.SUCCESS | 1143 response.error_code = sync_enums_pb2.SyncEnums.SUCCESS |
1133 self.CheckStoreBirthday(request) | 1144 self.CheckStoreBirthday(request) |
1134 response.store_birthday = self.account.store_birthday | 1145 response.store_birthday = self.account.store_birthday |
1135 self.CheckTransientError(); | 1146 self.CheckTransientError() |
1136 self.CheckSendError(); | 1147 self.CheckSendError() |
1137 | 1148 |
1138 print_context('->') | 1149 print_context('->') |
1139 | 1150 |
1140 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: | 1151 if contents == sync_pb2.ClientToServerMessage.AUTHENTICATE: |
1141 print 'Authenticate' | 1152 print 'Authenticate' |
1142 # We accept any authentication token, and support only one account. | 1153 # We accept any authentication token, and support only one account. |
1143 # TODO(nick): Mock out the GAIA authentication as well; hook up here. | 1154 # TODO(nick): Mock out the GAIA authentication as well; hook up here. |
1144 response.authenticate.user.email = 'syncjuser@chromium' | 1155 response.authenticate.user.email = 'syncjuser@chromium' |
1145 response.authenticate.user.display_name = 'Sync J User' | 1156 response.authenticate.user.display_name = 'Sync J User' |
1146 elif contents == sync_pb2.ClientToServerMessage.COMMIT: | 1157 elif contents == sync_pb2.ClientToServerMessage.COMMIT: |
1147 print 'Commit %d item(s)' % len(request.commit.entries) | 1158 print 'Commit %d item(s)' % len(request.commit.entries) |
1148 self.HandleCommit(request.commit, response.commit) | 1159 self.HandleCommit(request.commit, response.commit) |
1149 elif contents == sync_pb2.ClientToServerMessage.GET_UPDATES: | 1160 elif contents == sync_pb2.ClientToServerMessage.GET_UPDATES: |
1150 print 'GetUpdates', | 1161 print 'GetUpdates', |
1151 self.HandleGetUpdates(request.get_updates, response.get_updates) | 1162 self.HandleGetUpdates(request.get_updates, response.get_updates) |
1152 print_context('<-') | 1163 print_context('<-') |
1153 print '%d update(s)' % len(response.get_updates.entries) | 1164 print '%d update(s)' % len(response.get_updates.entries) |
1165 elif contents == sync_pb2.ClientToServerMessage.GET_KEY: | |
1166 print 'GetKey', | |
1167 self.HandleGetKey(request.get_key, response.get_key) | |
1154 else: | 1168 else: |
1155 print 'Unrecognizable sync request!' | 1169 print 'Unrecognizable sync request!' |
1156 return (400, None) # Bad request. | 1170 return (400, None) # Bad request. |
1157 return (200, response.SerializeToString()) | 1171 return (200, response.SerializeToString()) |
1158 except MigrationDoneError, error: | 1172 except MigrationDoneError, error: |
1159 print_context('<-') | 1173 print_context('<-') |
1160 print 'MIGRATION_DONE: <%s>' % (ShortDatatypeListSummary(error.datatypes)) | 1174 print 'MIGRATION_DONE: <%s>' % (ShortDatatypeListSummary(error.datatypes)) |
1161 response = sync_pb2.ClientToServerResponse() | 1175 response = sync_pb2.ClientToServerResponse() |
1162 response.store_birthday = self.account.store_birthday | 1176 response.store_birthday = self.account.store_birthday |
1163 response.error_code = sync_enums_pb2.SyncEnums.MIGRATION_DONE | 1177 response.error_code = sync_enums_pb2.SyncEnums.MIGRATION_DONE |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1255 | 1269 |
1256 update_sieve.CheckMigrationState() | 1270 update_sieve.CheckMigrationState() |
1257 | 1271 |
1258 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) | 1272 new_timestamp, entries, remaining = self.account.GetChanges(update_sieve) |
1259 | 1273 |
1260 update_response.changes_remaining = remaining | 1274 update_response.changes_remaining = remaining |
1261 for entry in entries: | 1275 for entry in entries: |
1262 reply = update_response.entries.add() | 1276 reply = update_response.entries.add() |
1263 reply.CopyFrom(entry) | 1277 reply.CopyFrom(entry) |
1264 update_sieve.SaveProgress(new_timestamp, update_response) | 1278 update_sieve.SaveProgress(new_timestamp, update_response) |
1279 | |
1280 def HandleGetKey(self, update_request, updates_response): | |
1281 """Respond to a GetKey request by returning the per-account encryption key. | |
1282 | |
1283 Args: | |
1284 update_request: A sync_pb:GetKeyMessage protobuf. | |
1285 update_response: A sync_pb.GetKeyResponse protobuf into which the the key | |
1286 is written. | |
1287 """ | |
1288 updates_response.SetInParent() | |
1289 updates_response.key = self.account.GetKey() | |
1290 print "Returning encryption key: %s" % (updates_response.key) | |
OLD | NEW |