blob: a61d5e53bdb39ea5012bf7b411408d8ae6428831 [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
7import datetime
8import os
9import time
10from xml.dom import minidom
11
12
13APP_ID = '87efface-864d-49a5-9bb3-4b050a7c227a'
14
15# Responses for the various Omaha protocols indexed by the protocol version.
16UPDATE_RESPONSE = {}
17UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
18 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
19 <daystart elapsed_seconds="%(time_elapsed)s"/>
20 <app appid="{%(appid)s}" status="ok">
21 <ping status="ok"/>
22 <updatecheck
23 ChromeOSVersion="9999.0.0"
24 codebase="%(url)s"
25 hash="%(sha1)s"
26 sha256="%(sha256)s"
27 needsadmin="false"
28 size="%(size)s"
Gilad Arnolde96e8652013-05-22 11:36:32 -070029 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080030 status="ok"
31 %(extra_attr)s/>
32 </app>
33 </gupdate>
34 """
35
36
37UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
38 <response protocol="3.0">
39 <daystart elapsed_seconds="%(time_elapsed)s"/>
40 <app appid="{%(appid)s}" status="ok">
41 <ping status="ok"/>
42 <updatecheck status="ok">
43 <urls>
44 <url codebase="%(codebase)s/"/>
45 </urls>
46 <manifest version="9999.0.0">
47 <packages>
48 <package hash="%(sha1)s" name="%(filename)s" size="%(size)s"
49 required="true"/>
50 </packages>
51 <actions>
52 <action event="postinstall"
53 ChromeOSVersion="9999.0.0"
54 sha256="%(sha256)s"
55 needsadmin="false"
Gilad Arnolde96e8652013-05-22 11:36:32 -070056 IsDeltaPayload="%(is_delta_format)s"
Chris Sosa52148582012-11-15 15:35:58 -080057 %(extra_attr)s />
58 </actions>
59 </manifest>
60 </updatecheck>
61 </app>
62 </response>
63 """
64
65
66# Responses for the various Omaha protocols indexed by the protocol version
67# when there's no update to be served.
68NO_UPDATE_RESPONSE = {}
69NO_UPDATE_RESPONSE['2.0'] = """<?xml version="1.0" encoding="UTF-8"?>
70 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
71 <daystart elapsed_seconds="%(time_elapsed)s"/>
72 <app appid="{%(appid)s}" status="ok">
73 <ping status="ok"/>
74 <updatecheck status="noupdate"/>
75 </app>
76 </gupdate>
77 """
78
79
80NO_UPDATE_RESPONSE['3.0'] = """<?xml version="1.0" encoding="UTF-8"?>
Jay Srinivasan00244952013-03-26 11:05:06 -070081 <response protocol="3.0">
Chris Sosa52148582012-11-15 15:35:58 -080082 <daystart elapsed_seconds="%(time_elapsed)s"/>
83 <app appid="{%(appid)s}" status="ok">
84 <ping status="ok"/>
85 <updatecheck status="noupdate"/>
86 </app>
87 </response>
88 """
89
90
91class UnknownProtocolRequestedException(Exception):
92 """Raised when an supported protocol is specified."""
93
94
95def GetSecondsSinceMidnight():
96 """Returns the seconds since midnight as a decimal value."""
97 now = time.localtime()
98 return now[3] * 3600 + now[4] * 60 + now[5]
99
100
101def GetCommonResponseValues():
102 """Returns a dictionary of default values for the response."""
103 response_values = {}
104 response_values['appid'] = APP_ID
105 response_values['time_elapsed'] = GetSecondsSinceMidnight()
106 return response_values
107
108
109def GetSubstitutedResponse(response_dict, protocol, response_values):
110 """Substitutes the protocol-specific response with response_values.
111
112 Args:
113 response_dict: Canned response messages indexed by protocol.
114 protocol: client's protocol version from the request Xml.
115 response_values: Values to be substituted in the canned response.
116 Returns:
117 Xml string to be passed back to client.
118 """
119 response_xml = response_dict[protocol] % response_values
120 return response_xml
121
122
David Zeuthen52ccd012013-10-31 12:58:26 -0700123def GetUpdateResponse(sha1, sha256, size, url, is_delta_format, metadata_size,
124 signed_metadata_hash, public_key, protocol,
Chris Sosa52148582012-11-15 15:35:58 -0800125 critical_update=False):
126 """Returns a protocol-specific response to the client for a new update.
127
128 Args:
129 sha1: SHA1 hash of update blob
130 sha256: SHA256 hash of update blob
131 size: size of update blob
132 url: where to find update blob
133 is_delta_format: true if url refers to a delta payload
David Zeuthen52ccd012013-10-31 12:58:26 -0700134 metadata_size: the size of the metadata, in bytes.
135 signed_metadata_hash: the signed metadata hash or None if not signed.
136 public_key: the public key to transmit to the client or None if no key.
Chris Sosa52148582012-11-15 15:35:58 -0800137 protocol: client's protocol version from the request Xml.
138 critical_update: whether this is a critical update.
139 Returns:
140 Xml string to be passed back to client.
141 """
142 response_values = GetCommonResponseValues()
143 response_values['sha1'] = sha1
144 response_values['sha256'] = sha256
145 response_values['size'] = size
146 response_values['url'] = url
147 (codebase, filename) = os.path.split(url)
148 response_values['codebase'] = codebase
149 response_values['filename'] = filename
Gilad Arnolde96e8652013-05-22 11:36:32 -0700150 response_values['is_delta_format'] = str(is_delta_format).lower()
Chris Sosa52148582012-11-15 15:35:58 -0800151 extra_attributes = []
152 if critical_update:
153 # The date string looks like '20111115' (2011-11-15). As of writing,
154 # there's no particular format for the deadline value that the
155 # client expects -- it's just empty vs. non-empty.
156 date_str = datetime.date.today().strftime('%Y%m%d')
157 extra_attributes.append('deadline="%s"' % date_str)
158
David Zeuthen52ccd012013-10-31 12:58:26 -0700159 if metadata_size:
160 extra_attributes.append('MetadataSize="%d"' % metadata_size)
161 if signed_metadata_hash:
162 extra_attributes.append('MetadataSignatureRsa="%s"' % signed_metadata_hash)
163 if public_key:
164 extra_attributes.append('PublicKeyRsa="%s"' % public_key)
165
Chris Sosa52148582012-11-15 15:35:58 -0800166 response_values['extra_attr'] = ' '.join(extra_attributes)
167 return GetSubstitutedResponse(UPDATE_RESPONSE, protocol, response_values)
168
169
170def GetNoUpdateResponse(protocol):
171 """Returns a protocol-specific response to the client for no update.
172
173 Args:
174 protocol: client's protocol version from the request Xml.
175 Returns:
176 Xml string to be passed back to client.
177 """
178 response_values = GetCommonResponseValues()
179 return GetSubstitutedResponse(NO_UPDATE_RESPONSE, protocol, response_values)
180
181
182def ParseUpdateRequest(request_string):
183 """Returns a tuple containing information parsed from an update request.
184
185 Args:
186 request_dom: an xml string containing the update request.
joychen121fc9b2013-08-02 14:30:30 -0700187 Returns:
188 Tuple consisting of protocol string, app element, event element, and
Chris Sosa52148582012-11-15 15:35:58 -0800189 update_check element.
190 Raises UnknownProtocolRequestedException if we do not understand the
191 protocol.
192 """
193 request_dom = minidom.parseString(request_string)
194 protocol = request_dom.firstChild.getAttribute('protocol')
195 supported_protocols = '2.0', '3.0'
196 if protocol not in supported_protocols:
197 raise UnknownProtocolRequestedException('Supported protocols are %s' %
198 supported_protocols)
199
200 element_dict = {}
201 for name in ['event', 'app', 'updatecheck']:
202 element_dict[name] = 'o:' + name if protocol == '2.0' else name
203
204 app = request_dom.firstChild.getElementsByTagName(element_dict['app'])[0]
205 event = request_dom.getElementsByTagName(element_dict['event'])
206 update_check = request_dom.getElementsByTagName(element_dict['updatecheck'])
207
208 return protocol, app, event, update_check