blob: 58d033b945f82e788460c56673c8de6c8a7c5254 [file] [log] [blame]
Chris Sosa52148582012-11-15 15:35:58 -08001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Module containing common autoupdate utilities and protocol dictionaries."""
6
Don Garrettfb15e322016-06-21 19:12:08 -07007from __future__ import print_function
8
Amin Hassani9e2adae2018-03-13 16:19:47 -07009import base64
10import binascii
Chris Sosa52148582012-11-15 15:35:58 -080011import datetime
12import os
13import time
14from xml.dom import minidom
15
Gilad Arnolde7819e72014-03-21 12:50:48 -070016# Update events and result codes.
17EVENT_TYPE_UNKNOWN = 0
18EVENT_TYPE_DOWNLOAD_COMPLETE = 1
19EVENT_TYPE_INSTALL_COMPLETE = 2
20EVENT_TYPE_UPDATE_COMPLETE = 3
21EVENT_TYPE_UPDATE_DOWNLOAD_STARTED = 13
22EVENT_TYPE_UPDATE_DOWNLOAD_FINISHED = 14
23
24EVENT_RESULT_ERROR = 0
25EVENT_RESULT_SUCCESS = 1
26EVENT_RESULT_SUCCESS_REBOOT = 2
27EVENT_RESULT_UPDATE_DEFERRED = 9
28
29
Chris Sosa52148582012-11-15 15:35:58 -080030# Responses for the various Omaha protocols indexed by the protocol version.
Gilad Arnolde7819e72014-03-21 12:50:48 -070031#
32# Update available responses:
33_UPDATE_RESPONSE = {}
34_UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080035 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
36 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070037 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080038 <ping status="ok"/>
39 <updatecheck
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070040 ChromeOSVersion="999999.0.0"
Chris Sosa52148582012-11-15 15:35:58 -080041 codebase="%(url)s"
42 hash="%(sha1)s"
43 sha256="%(sha256)s"
44 needsadmin="false"
45 size="%(size)s"
Gilad Arnolde96e8652013-05-22 11:36:32 -070046 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080047 status="ok"
48 %(extra_attr)s/>
49 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070050 </gupdate>"""
51_UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080052 <response protocol="3.0">
53 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070054 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080055 <ping status="ok"/>
56 <updatecheck status="ok">
57 <urls>
58 <url codebase="%(codebase)s/"/>
59 </urls>
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070060 <manifest version="999999.0.0">
Chris Sosa52148582012-11-15 15:35:58 -080061 <packages>
62 <package hash="%(sha1)s" name="%(filename)s" size="%(size)s"
Amin Hassani9e2adae2018-03-13 16:19:47 -070063 hash_sha256="%(hash_sha256)s" required="true"/>
Chris Sosa52148582012-11-15 15:35:58 -080064 </packages>
65 <actions>
66 <action event="postinstall"
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070067 ChromeOSVersion="999999.0.0"
Chris Sosa52148582012-11-15 15:35:58 -080068 sha256="%(sha256)s"
69 needsadmin="false"
Gilad Arnolde96e8652013-05-22 11:36:32 -070070 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080071 %(extra_attr)s />
72 </actions>
73 </manifest>
74 </updatecheck>
75 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070076 </response>"""
Chris Sosa52148582012-11-15 15:35:58 -080077
Gilad Arnolde7819e72014-03-21 12:50:48 -070078# No update responses:
79_NO_UPDATE_RESPONSE = {}
80_NO_UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080081 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
82 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070083 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080084 <ping status="ok"/>
85 <updatecheck status="noupdate"/>
86 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070087 </gupdate>"""
88_NO_UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Jay Srinivasan00244952013-03-26 11:05:06 -070089 <response protocol="3.0">
Chris Sosa52148582012-11-15 15:35:58 -080090 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070091 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080092 <ping status="ok"/>
93 <updatecheck status="noupdate"/>
94 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070095 </response>"""
96
97
98# Non-update event responses:
99_EVENT_RESPONSE = {}
100_EVENT_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
101 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
102 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -0700103 <app appid="%(appid)s" status="ok">
Gilad Arnolde7819e72014-03-21 12:50:48 -0700104 <ping status="ok"/>
105 <event status="ok"/>
106 </app>
107 </gupdate>"""
108_EVENT_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
109 <response protocol="3.0">
110 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -0700111 <app appid="%(appid)s" status="ok">
Gilad Arnolde7819e72014-03-21 12:50:48 -0700112 <ping status="ok"/>
113 <event status="ok"/>
114 </app>
115 </response>"""
Chris Sosa52148582012-11-15 15:35:58 -0800116
117
118class UnknownProtocolRequestedException(Exception):
119 """Raised when an supported protocol is specified."""
120
121
122def GetSecondsSinceMidnight():
123 """Returns the seconds since midnight as a decimal value."""
124 now = time.localtime()
125 return now[3] * 3600 + now[4] * 60 + now[5]
126
127
Amin Hassanid7a913a2018-03-13 15:19:24 -0700128def GetCommonResponseValues(appid):
Chris Sosa52148582012-11-15 15:35:58 -0800129 """Returns a dictionary of default values for the response."""
130 response_values = {}
Amin Hassanid7a913a2018-03-13 15:19:24 -0700131 response_values['appid'] = appid
Chris Sosa52148582012-11-15 15:35:58 -0800132 response_values['time_elapsed'] = GetSecondsSinceMidnight()
133 return response_values
134
135
136def GetSubstitutedResponse(response_dict, protocol, response_values):
137 """Substitutes the protocol-specific response with response_values.
138
139 Args:
140 response_dict: Canned response messages indexed by protocol.
141 protocol: client's protocol version from the request Xml.
142 response_values: Values to be substituted in the canned response.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700143
Chris Sosa52148582012-11-15 15:35:58 -0800144 Returns:
145 Xml string to be passed back to client.
146 """
147 response_xml = response_dict[protocol] % response_values
148 return response_xml
149
150
David Zeuthen52ccd012013-10-31 12:58:26 -0700151def GetUpdateResponse(sha1, sha256, size, url, is_delta_format, metadata_size,
Amin Hassanid7a913a2018-03-13 15:19:24 -0700152 signed_metadata_hash, public_key, protocol, appid,
Chris Sosa52148582012-11-15 15:35:58 -0800153 critical_update=False):
154 """Returns a protocol-specific response to the client for a new update.
155
156 Args:
157 sha1: SHA1 hash of update blob
158 sha256: SHA256 hash of update blob
159 size: size of update blob
160 url: where to find update blob
161 is_delta_format: true if url refers to a delta payload
David Zeuthen52ccd012013-10-31 12:58:26 -0700162 metadata_size: the size of the metadata, in bytes.
163 signed_metadata_hash: the signed metadata hash or None if not signed.
164 public_key: the public key to transmit to the client or None if no key.
Chris Sosa52148582012-11-15 15:35:58 -0800165 protocol: client's protocol version from the request Xml.
Amin Hassanid7a913a2018-03-13 15:19:24 -0700166 appid: the appid associated with the response.
Chris Sosa52148582012-11-15 15:35:58 -0800167 critical_update: whether this is a critical update.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700168
Chris Sosa52148582012-11-15 15:35:58 -0800169 Returns:
170 Xml string to be passed back to client.
171 """
Amin Hassanid7a913a2018-03-13 15:19:24 -0700172 response_values = GetCommonResponseValues(appid)
Chris Sosa52148582012-11-15 15:35:58 -0800173 response_values['sha1'] = sha1
174 response_values['sha256'] = sha256
Amin Hassani9e2adae2018-03-13 16:19:47 -0700175 # sha256 is base64 encoded, encode it to hex.
176 response_values['hash_sha256'] = binascii.hexlify(base64.b64decode(sha256))
Chris Sosa52148582012-11-15 15:35:58 -0800177 response_values['size'] = size
178 response_values['url'] = url
179 (codebase, filename) = os.path.split(url)
180 response_values['codebase'] = codebase
181 response_values['filename'] = filename
Gilad Arnolde96e8652013-05-22 11:36:32 -0700182 response_values['is_delta_format'] = str(is_delta_format).lower()
Chris Sosa52148582012-11-15 15:35:58 -0800183 extra_attributes = []
184 if critical_update:
185 # The date string looks like '20111115' (2011-11-15). As of writing,
186 # there's no particular format for the deadline value that the
187 # client expects -- it's just empty vs. non-empty.
188 date_str = datetime.date.today().strftime('%Y%m%d')
189 extra_attributes.append('deadline="%s"' % date_str)
190
David Zeuthen52ccd012013-10-31 12:58:26 -0700191 if metadata_size:
192 extra_attributes.append('MetadataSize="%d"' % metadata_size)
193 if signed_metadata_hash:
194 extra_attributes.append('MetadataSignatureRsa="%s"' % signed_metadata_hash)
195 if public_key:
196 extra_attributes.append('PublicKeyRsa="%s"' % public_key)
197
Chris Sosa52148582012-11-15 15:35:58 -0800198 response_values['extra_attr'] = ' '.join(extra_attributes)
Gilad Arnolde7819e72014-03-21 12:50:48 -0700199 return GetSubstitutedResponse(_UPDATE_RESPONSE, protocol, response_values)
Chris Sosa52148582012-11-15 15:35:58 -0800200
201
Amin Hassanid7a913a2018-03-13 15:19:24 -0700202def GetNoUpdateResponse(protocol, appid):
Chris Sosa52148582012-11-15 15:35:58 -0800203 """Returns a protocol-specific response to the client for no update.
204
205 Args:
206 protocol: client's protocol version from the request Xml.
Amin Hassanid7a913a2018-03-13 15:19:24 -0700207 appid: the appid associated with the response.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700208
Chris Sosa52148582012-11-15 15:35:58 -0800209 Returns:
210 Xml string to be passed back to client.
211 """
Amin Hassanid7a913a2018-03-13 15:19:24 -0700212 response_values = GetCommonResponseValues(appid)
Gilad Arnolde7819e72014-03-21 12:50:48 -0700213 return GetSubstitutedResponse(_NO_UPDATE_RESPONSE, protocol, response_values)
214
215
Amin Hassanid7a913a2018-03-13 15:19:24 -0700216def GetEventResponse(protocol, appid):
Gilad Arnolde7819e72014-03-21 12:50:48 -0700217 """Returns a protocol-specific response to a client event notification.
218
219 Args:
220 protocol: client's protocol version from the request Xml.
Amin Hassanid7a913a2018-03-13 15:19:24 -0700221 appid: the appid associated with the response.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700222
223 Returns:
224 Xml string to be passed back to client.
225 """
Amin Hassanid7a913a2018-03-13 15:19:24 -0700226 response_values = GetCommonResponseValues(appid)
Gilad Arnolde7819e72014-03-21 12:50:48 -0700227 return GetSubstitutedResponse(_EVENT_RESPONSE, protocol, response_values)
Chris Sosa52148582012-11-15 15:35:58 -0800228
229
230def ParseUpdateRequest(request_string):
231 """Returns a tuple containing information parsed from an update request.
232
233 Args:
Gilad Arnolde7819e72014-03-21 12:50:48 -0700234 request_string: an xml string containing the update request.
235
joychen121fc9b2013-08-02 14:30:30 -0700236 Returns:
237 Tuple consisting of protocol string, app element, event element, and
Chris Sosa52148582012-11-15 15:35:58 -0800238 update_check element.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700239
Chris Sosa52148582012-11-15 15:35:58 -0800240 Raises UnknownProtocolRequestedException if we do not understand the
241 protocol.
242 """
243 request_dom = minidom.parseString(request_string)
244 protocol = request_dom.firstChild.getAttribute('protocol')
245 supported_protocols = '2.0', '3.0'
246 if protocol not in supported_protocols:
247 raise UnknownProtocolRequestedException('Supported protocols are %s' %
248 supported_protocols)
249
250 element_dict = {}
251 for name in ['event', 'app', 'updatecheck']:
252 element_dict[name] = 'o:' + name if protocol == '2.0' else name
253
254 app = request_dom.firstChild.getElementsByTagName(element_dict['app'])[0]
255 event = request_dom.getElementsByTagName(element_dict['event'])
256 update_check = request_dom.getElementsByTagName(element_dict['updatecheck'])
257
258 return protocol, app, event, update_check