blob: 95a52541b99ecf5cf06fc7eb4d82d21c168d7a54 [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
Chris Sosa52148582012-11-15 15:35:58 -08009import datetime
10import os
11import time
12from xml.dom import minidom
13
Gilad Arnolde7819e72014-03-21 12:50:48 -070014# Update events and result codes.
15EVENT_TYPE_UNKNOWN = 0
16EVENT_TYPE_DOWNLOAD_COMPLETE = 1
17EVENT_TYPE_INSTALL_COMPLETE = 2
18EVENT_TYPE_UPDATE_COMPLETE = 3
19EVENT_TYPE_UPDATE_DOWNLOAD_STARTED = 13
20EVENT_TYPE_UPDATE_DOWNLOAD_FINISHED = 14
21
22EVENT_RESULT_ERROR = 0
23EVENT_RESULT_SUCCESS = 1
24EVENT_RESULT_SUCCESS_REBOOT = 2
25EVENT_RESULT_UPDATE_DEFERRED = 9
26
27
Chris Sosa52148582012-11-15 15:35:58 -080028# Responses for the various Omaha protocols indexed by the protocol version.
Gilad Arnolde7819e72014-03-21 12:50:48 -070029#
30# Update available responses:
31_UPDATE_RESPONSE = {}
32_UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080033 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
34 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070035 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080036 <ping status="ok"/>
37 <updatecheck
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070038 ChromeOSVersion="999999.0.0"
Chris Sosa52148582012-11-15 15:35:58 -080039 codebase="%(url)s"
40 hash="%(sha1)s"
41 sha256="%(sha256)s"
42 needsadmin="false"
43 size="%(size)s"
Gilad Arnolde96e8652013-05-22 11:36:32 -070044 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080045 status="ok"
46 %(extra_attr)s/>
47 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070048 </gupdate>"""
49_UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080050 <response protocol="3.0">
51 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070052 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080053 <ping status="ok"/>
54 <updatecheck status="ok">
55 <urls>
56 <url codebase="%(codebase)s/"/>
57 </urls>
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070058 <manifest version="999999.0.0">
Chris Sosa52148582012-11-15 15:35:58 -080059 <packages>
60 <package hash="%(sha1)s" name="%(filename)s" size="%(size)s"
61 required="true"/>
62 </packages>
63 <actions>
64 <action event="postinstall"
Paul Hobbs5e7b5a72017-10-04 11:02:39 -070065 ChromeOSVersion="999999.0.0"
Chris Sosa52148582012-11-15 15:35:58 -080066 sha256="%(sha256)s"
67 needsadmin="false"
Gilad Arnolde96e8652013-05-22 11:36:32 -070068 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080069 %(extra_attr)s />
70 </actions>
71 </manifest>
72 </updatecheck>
73 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070074 </response>"""
Chris Sosa52148582012-11-15 15:35:58 -080075
Gilad Arnolde7819e72014-03-21 12:50:48 -070076# No update responses:
77_NO_UPDATE_RESPONSE = {}
78_NO_UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Chris Sosa52148582012-11-15 15:35:58 -080079 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
80 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070081 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080082 <ping status="ok"/>
83 <updatecheck status="noupdate"/>
84 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070085 </gupdate>"""
86_NO_UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Jay Srinivasan00244952013-03-26 11:05:06 -070087 <response protocol="3.0">
Chris Sosa52148582012-11-15 15:35:58 -080088 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -070089 <app appid="%(appid)s" status="ok">
Chris Sosa52148582012-11-15 15:35:58 -080090 <ping status="ok"/>
91 <updatecheck status="noupdate"/>
92 </app>
Gilad Arnolde7819e72014-03-21 12:50:48 -070093 </response>"""
94
95
96# Non-update event responses:
97_EVENT_RESPONSE = {}
98_EVENT_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
99 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
100 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -0700101 <app appid="%(appid)s" status="ok">
Gilad Arnolde7819e72014-03-21 12:50:48 -0700102 <ping status="ok"/>
103 <event status="ok"/>
104 </app>
105 </gupdate>"""
106_EVENT_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
107 <response protocol="3.0">
108 <daystart elapsed_seconds="%(time_elapsed)s"/>
Amin Hassanid7a913a2018-03-13 15:19:24 -0700109 <app appid="%(appid)s" status="ok">
Gilad Arnolde7819e72014-03-21 12:50:48 -0700110 <ping status="ok"/>
111 <event status="ok"/>
112 </app>
113 </response>"""
Chris Sosa52148582012-11-15 15:35:58 -0800114
115
116class UnknownProtocolRequestedException(Exception):
117 """Raised when an supported protocol is specified."""
118
119
120def GetSecondsSinceMidnight():
121 """Returns the seconds since midnight as a decimal value."""
122 now = time.localtime()
123 return now[3] * 3600 + now[4] * 60 + now[5]
124
125
Amin Hassanid7a913a2018-03-13 15:19:24 -0700126def GetCommonResponseValues(appid):
Chris Sosa52148582012-11-15 15:35:58 -0800127 """Returns a dictionary of default values for the response."""
128 response_values = {}
Amin Hassanid7a913a2018-03-13 15:19:24 -0700129 response_values['appid'] = appid
Chris Sosa52148582012-11-15 15:35:58 -0800130 response_values['time_elapsed'] = GetSecondsSinceMidnight()
131 return response_values
132
133
134def GetSubstitutedResponse(response_dict, protocol, response_values):
135 """Substitutes the protocol-specific response with response_values.
136
137 Args:
138 response_dict: Canned response messages indexed by protocol.
139 protocol: client's protocol version from the request Xml.
140 response_values: Values to be substituted in the canned response.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700141
Chris Sosa52148582012-11-15 15:35:58 -0800142 Returns:
143 Xml string to be passed back to client.
144 """
145 response_xml = response_dict[protocol] % response_values
146 return response_xml
147
148
David Zeuthen52ccd012013-10-31 12:58:26 -0700149def GetUpdateResponse(sha1, sha256, size, url, is_delta_format, metadata_size,
Amin Hassanid7a913a2018-03-13 15:19:24 -0700150 signed_metadata_hash, public_key, protocol, appid,
Chris Sosa52148582012-11-15 15:35:58 -0800151 critical_update=False):
152 """Returns a protocol-specific response to the client for a new update.
153
154 Args:
155 sha1: SHA1 hash of update blob
156 sha256: SHA256 hash of update blob
157 size: size of update blob
158 url: where to find update blob
159 is_delta_format: true if url refers to a delta payload
David Zeuthen52ccd012013-10-31 12:58:26 -0700160 metadata_size: the size of the metadata, in bytes.
161 signed_metadata_hash: the signed metadata hash or None if not signed.
162 public_key: the public key to transmit to the client or None if no key.
Chris Sosa52148582012-11-15 15:35:58 -0800163 protocol: client's protocol version from the request Xml.
Amin Hassanid7a913a2018-03-13 15:19:24 -0700164 appid: the appid associated with the response.
Chris Sosa52148582012-11-15 15:35:58 -0800165 critical_update: whether this is a critical update.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700166
Chris Sosa52148582012-11-15 15:35:58 -0800167 Returns:
168 Xml string to be passed back to client.
169 """
Amin Hassanid7a913a2018-03-13 15:19:24 -0700170 response_values = GetCommonResponseValues(appid)
Chris Sosa52148582012-11-15 15:35:58 -0800171 response_values['sha1'] = sha1
172 response_values['sha256'] = sha256
173 response_values['size'] = size
174 response_values['url'] = url
175 (codebase, filename) = os.path.split(url)
176 response_values['codebase'] = codebase
177 response_values['filename'] = filename
Gilad Arnolde96e8652013-05-22 11:36:32 -0700178 response_values['is_delta_format'] = str(is_delta_format).lower()
Chris Sosa52148582012-11-15 15:35:58 -0800179 extra_attributes = []
180 if critical_update:
181 # The date string looks like '20111115' (2011-11-15). As of writing,
182 # there's no particular format for the deadline value that the
183 # client expects -- it's just empty vs. non-empty.
184 date_str = datetime.date.today().strftime('%Y%m%d')
185 extra_attributes.append('deadline="%s"' % date_str)
186
David Zeuthen52ccd012013-10-31 12:58:26 -0700187 if metadata_size:
188 extra_attributes.append('MetadataSize="%d"' % metadata_size)
189 if signed_metadata_hash:
190 extra_attributes.append('MetadataSignatureRsa="%s"' % signed_metadata_hash)
191 if public_key:
192 extra_attributes.append('PublicKeyRsa="%s"' % public_key)
193
Chris Sosa52148582012-11-15 15:35:58 -0800194 response_values['extra_attr'] = ' '.join(extra_attributes)
Gilad Arnolde7819e72014-03-21 12:50:48 -0700195 return GetSubstitutedResponse(_UPDATE_RESPONSE, protocol, response_values)
Chris Sosa52148582012-11-15 15:35:58 -0800196
197
Amin Hassanid7a913a2018-03-13 15:19:24 -0700198def GetNoUpdateResponse(protocol, appid):
Chris Sosa52148582012-11-15 15:35:58 -0800199 """Returns a protocol-specific response to the client for no update.
200
201 Args:
202 protocol: client's protocol version from the request Xml.
Amin Hassanid7a913a2018-03-13 15:19:24 -0700203 appid: the appid associated with the response.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700204
Chris Sosa52148582012-11-15 15:35:58 -0800205 Returns:
206 Xml string to be passed back to client.
207 """
Amin Hassanid7a913a2018-03-13 15:19:24 -0700208 response_values = GetCommonResponseValues(appid)
Gilad Arnolde7819e72014-03-21 12:50:48 -0700209 return GetSubstitutedResponse(_NO_UPDATE_RESPONSE, protocol, response_values)
210
211
Amin Hassanid7a913a2018-03-13 15:19:24 -0700212def GetEventResponse(protocol, appid):
Gilad Arnolde7819e72014-03-21 12:50:48 -0700213 """Returns a protocol-specific response to a client event notification.
214
215 Args:
216 protocol: client's protocol version from the request Xml.
Amin Hassanid7a913a2018-03-13 15:19:24 -0700217 appid: the appid associated with the response.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700218
219 Returns:
220 Xml string to be passed back to client.
221 """
Amin Hassanid7a913a2018-03-13 15:19:24 -0700222 response_values = GetCommonResponseValues(appid)
Gilad Arnolde7819e72014-03-21 12:50:48 -0700223 return GetSubstitutedResponse(_EVENT_RESPONSE, protocol, response_values)
Chris Sosa52148582012-11-15 15:35:58 -0800224
225
226def ParseUpdateRequest(request_string):
227 """Returns a tuple containing information parsed from an update request.
228
229 Args:
Gilad Arnolde7819e72014-03-21 12:50:48 -0700230 request_string: an xml string containing the update request.
231
joychen121fc9b2013-08-02 14:30:30 -0700232 Returns:
233 Tuple consisting of protocol string, app element, event element, and
Chris Sosa52148582012-11-15 15:35:58 -0800234 update_check element.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700235
Chris Sosa52148582012-11-15 15:35:58 -0800236 Raises UnknownProtocolRequestedException if we do not understand the
237 protocol.
238 """
239 request_dom = minidom.parseString(request_string)
240 protocol = request_dom.firstChild.getAttribute('protocol')
241 supported_protocols = '2.0', '3.0'
242 if protocol not in supported_protocols:
243 raise UnknownProtocolRequestedException('Supported protocols are %s' %
244 supported_protocols)
245
246 element_dict = {}
247 for name in ['event', 'app', 'updatecheck']:
248 element_dict[name] = 'o:' + name if protocol == '2.0' else name
249
250 app = request_dom.firstChild.getElementsByTagName(element_dict['app'])[0]
251 event = request_dom.getElementsByTagName(element_dict['event'])
252 update_check = request_dom.getElementsByTagName(element_dict['updatecheck'])
253
254 return protocol, app, event, update_check