blob: 23091e982ad565c57615ff3d60e814be3f8ac0be [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 Hassanif9265b92018-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
16
Gilad Arnolde7819e72014-03-21 12:50:48 -070017# Update events and result codes.
18EVENT_TYPE_UNKNOWN = 0
19EVENT_TYPE_DOWNLOAD_COMPLETE = 1
20EVENT_TYPE_INSTALL_COMPLETE = 2
21EVENT_TYPE_UPDATE_COMPLETE = 3
22EVENT_TYPE_UPDATE_DOWNLOAD_STARTED = 13
23EVENT_TYPE_UPDATE_DOWNLOAD_FINISHED = 14
24
25EVENT_RESULT_ERROR = 0
26EVENT_RESULT_SUCCESS = 1
27EVENT_RESULT_SUCCESS_REBOOT = 2
28EVENT_RESULT_UPDATE_DEFERRED = 9
29
30
31# A default app_id value.
32_APP_ID = '87efface-864d-49a5-9bb3-4b050a7c227a'
33
Chris Sosa52148582012-11-15 15:35:58 -080034
35# Responses for the various Omaha protocols indexed by the protocol version.
Gilad Arnolde7819e72014-03-21 12:50:48 -070036#
37# Update available responses:
38_UPDATE_RESPONSE = {}
39_UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080040 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
41 <daystart elapsed_seconds="%(time_elapsed)s"/>
42 <app appid="{%(appid)s}" status="ok">
43 <ping status="ok"/>
44 <updatecheck
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070045 ChromeOSVersion="999999.0.0"
Chris Sosa52148582012-11-15 15:35:58 -080046 codebase="%(url)s"
47 hash="%(sha1)s"
48 sha256="%(sha256)s"
49 needsadmin="false"
50 size="%(size)s"
Gilad Arnolde96e8652013-05-22 11:36:32 -070051 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080052 status="ok"
53 %(extra_attr)s/>
54 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070055 </gupdate>"""
56_UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080057 <response protocol="3.0">
58 <daystart elapsed_seconds="%(time_elapsed)s"/>
59 <app appid="{%(appid)s}" status="ok">
60 <ping status="ok"/>
61 <updatecheck status="ok">
62 <urls>
63 <url codebase="%(codebase)s/"/>
64 </urls>
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070065 <manifest version="999999.0.0">
Chris Sosa52148582012-11-15 15:35:58 -080066 <packages>
67 <package hash="%(sha1)s" name="%(filename)s" size="%(size)s"
Amin Hassanif9265b92018-03-13 16:19:47 -070068 hash_sha256="%(hash_sha256)s" required="true"/>
Chris Sosa52148582012-11-15 15:35:58 -080069 </packages>
70 <actions>
71 <action event="postinstall"
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070072 ChromeOSVersion="999999.0.0"
Chris Sosa52148582012-11-15 15:35:58 -080073 sha256="%(sha256)s"
74 needsadmin="false"
Gilad Arnolde96e8652013-05-22 11:36:32 -070075 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080076 %(extra_attr)s />
77 </actions>
78 </manifest>
79 </updatecheck>
80 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070081 </response>"""
Chris Sosa52148582012-11-15 15:35:58 -080082
Gilad Arnolde7819e72014-03-21 12:50:48 -070083# No update responses:
84_NO_UPDATE_RESPONSE = {}
85_NO_UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080086 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
87 <daystart elapsed_seconds="%(time_elapsed)s"/>
88 <app appid="{%(appid)s}" status="ok">
89 <ping status="ok"/>
90 <updatecheck status="noupdate"/>
91 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070092 </gupdate>"""
93_NO_UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Jay Srinivasan00244952013-03-26 11:05:06 -070094 <response protocol="3.0">
Chris Sosa52148582012-11-15 15:35:58 -080095 <daystart elapsed_seconds="%(time_elapsed)s"/>
96 <app appid="{%(appid)s}" status="ok">
97 <ping status="ok"/>
98 <updatecheck status="noupdate"/>
99 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -0700100 </response>"""
101
102
103# Non-update event responses:
104_EVENT_RESPONSE = {}
105_EVENT_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
106 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
107 <daystart elapsed_seconds="%(time_elapsed)s"/>
108 <app appid="{%(appid)s}" status="ok">
109 <ping status="ok"/>
110 <event status="ok"/>
111 </app>
112 </gupdate>"""
113_EVENT_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
114 <response protocol="3.0">
115 <daystart elapsed_seconds="%(time_elapsed)s"/>
116 <app appid="{%(appid)s}" status="ok">
117 <ping status="ok"/>
118 <event status="ok"/>
119 </app>
120 </response>"""
Chris Sosa52148582012-11-15 15:35:58 -0800121
122
123class UnknownProtocolRequestedException(Exception):
124 """Raised when an supported protocol is specified."""
125
126
127def GetSecondsSinceMidnight():
128 """Returns the seconds since midnight as a decimal value."""
129 now = time.localtime()
130 return now[3] * 3600 + now[4] * 60 + now[5]
131
132
133def GetCommonResponseValues():
134 """Returns a dictionary of default values for the response."""
135 response_values = {}
Gilad Arnolde7819e72014-03-21 12:50:48 -0700136 response_values['appid'] = _APP_ID
Chris Sosa52148582012-11-15 15:35:58 -0800137 response_values['time_elapsed'] = GetSecondsSinceMidnight()
138 return response_values
139
140
141def GetSubstitutedResponse(response_dict, protocol, response_values):
142 """Substitutes the protocol-specific response with response_values.
143
144 Args:
145 response_dict: Canned response messages indexed by protocol.
146 protocol: client's protocol version from the request Xml.
147 response_values: Values to be substituted in the canned response.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700148
Chris Sosa52148582012-11-15 15:35:58 -0800149 Returns:
150 Xml string to be passed back to client.
151 """
152 response_xml = response_dict[protocol] % response_values
153 return response_xml
154
155
David Zeuthen52ccd012013-10-31 12:58:26 -0700156def GetUpdateResponse(sha1, sha256, size, url, is_delta_format, metadata_size,
157 signed_metadata_hash, public_key, protocol,
Chris Sosa52148582012-11-15 15:35:58 -0800158 critical_update=False):
159 """Returns a protocol-specific response to the client for a new update.
160
161 Args:
162 sha1: SHA1 hash of update blob
163 sha256: SHA256 hash of update blob
164 size: size of update blob
165 url: where to find update blob
166 is_delta_format: true if url refers to a delta payload
David Zeuthen52ccd012013-10-31 12:58:26 -0700167 metadata_size: the size of the metadata, in bytes.
168 signed_metadata_hash: the signed metadata hash or None if not signed.
169 public_key: the public key to transmit to the client or None if no key.
Chris Sosa52148582012-11-15 15:35:58 -0800170 protocol: client's protocol version from the request Xml.
171 critical_update: whether this is a critical update.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700172
Chris Sosa52148582012-11-15 15:35:58 -0800173 Returns:
174 Xml string to be passed back to client.
175 """
176 response_values = GetCommonResponseValues()
177 response_values['sha1'] = sha1
178 response_values['sha256'] = sha256
Amin Hassanif9265b92018-03-13 16:19:47 -0700179 # sha256 is base64 encoded, encode it to hex.
180 response_values['hash_sha256'] = binascii.hexlify(base64.b64decode(sha256))
Chris Sosa52148582012-11-15 15:35:58 -0800181 response_values['size'] = size
182 response_values['url'] = url
183 (codebase, filename) = os.path.split(url)
184 response_values['codebase'] = codebase
185 response_values['filename'] = filename
Gilad Arnolde96e8652013-05-22 11:36:32 -0700186 response_values['is_delta_format'] = str(is_delta_format).lower()
Chris Sosa52148582012-11-15 15:35:58 -0800187 extra_attributes = []
188 if critical_update:
189 # The date string looks like '20111115' (2011-11-15). As of writing,
190 # there's no particular format for the deadline value that the
191 # client expects -- it's just empty vs. non-empty.
192 date_str = datetime.date.today().strftime('%Y%m%d')
193 extra_attributes.append('deadline="%s"' % date_str)
194
David Zeuthen52ccd012013-10-31 12:58:26 -0700195 if metadata_size:
196 extra_attributes.append('MetadataSize="%d"' % metadata_size)
197 if signed_metadata_hash:
198 extra_attributes.append('MetadataSignatureRsa="%s"' % signed_metadata_hash)
199 if public_key:
200 extra_attributes.append('PublicKeyRsa="%s"' % public_key)
201
Chris Sosa52148582012-11-15 15:35:58 -0800202 response_values['extra_attr'] = ' '.join(extra_attributes)
Gilad Arnolde7819e72014-03-21 12:50:48 -0700203 return GetSubstitutedResponse(_UPDATE_RESPONSE, protocol, response_values)
Chris Sosa52148582012-11-15 15:35:58 -0800204
205
206def GetNoUpdateResponse(protocol):
207 """Returns a protocol-specific response to the client for no update.
208
209 Args:
210 protocol: client's protocol version from the request Xml.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700211
Chris Sosa52148582012-11-15 15:35:58 -0800212 Returns:
213 Xml string to be passed back to client.
214 """
215 response_values = GetCommonResponseValues()
Gilad Arnolde7819e72014-03-21 12:50:48 -0700216 return GetSubstitutedResponse(_NO_UPDATE_RESPONSE, protocol, response_values)
217
218
219def GetEventResponse(protocol):
220 """Returns a protocol-specific response to a client event notification.
221
222 Args:
223 protocol: client's protocol version from the request Xml.
224
225 Returns:
226 Xml string to be passed back to client.
227 """
228 response_values = GetCommonResponseValues()
229 return GetSubstitutedResponse(_EVENT_RESPONSE, protocol, response_values)
Chris Sosa52148582012-11-15 15:35:58 -0800230
231
232def ParseUpdateRequest(request_string):
233 """Returns a tuple containing information parsed from an update request.
234
235 Args:
Gilad Arnolde7819e72014-03-21 12:50:48 -0700236 request_string: an xml string containing the update request.
237
joychen121fc9b2013-08-02 14:30:30 -0700238 Returns:
239 Tuple consisting of protocol string, app element, event element, and
Chris Sosa52148582012-11-15 15:35:58 -0800240 update_check element.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700241
Chris Sosa52148582012-11-15 15:35:58 -0800242 Raises UnknownProtocolRequestedException if we do not understand the
243 protocol.
244 """
245 request_dom = minidom.parseString(request_string)
246 protocol = request_dom.firstChild.getAttribute('protocol')
247 supported_protocols = '2.0', '3.0'
248 if protocol not in supported_protocols:
249 raise UnknownProtocolRequestedException('Supported protocols are %s' %
250 supported_protocols)
251
252 element_dict = {}
253 for name in ['event', 'app', 'updatecheck']:
254 element_dict[name] = 'o:' + name if protocol == '2.0' else name
255
256 app = request_dom.firstChild.getElementsByTagName(element_dict['app'])[0]
257 event = request_dom.getElementsByTagName(element_dict['event'])
258 update_check = request_dom.getElementsByTagName(element_dict['updatecheck'])
259
260 return protocol, app, event, update_check