Chromium Code Reviews| 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 |