Skip to main content

omaha_client/protocol/
response.rs

1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9#[cfg(test)]
10mod tests;
11
12use crate::protocol::Cohort;
13use serde::Deserialize;
14use serde_json::{Map, Value};
15
16/// An Omaha protocol response.
17///
18/// This holds the data for a response from the Omaha service.
19///
20/// See https://github.com/google/omaha/blob/HEAD/doc/ServerProtocolV3.md#response
21#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
22pub struct Response {
23    /// The current Omaha protocol version (which this is meant to be used with, is 3.0.  This
24    /// should always be set to "3.0".
25    ///
26    /// This is the 'protocol' attribute of the response object.
27    #[serde(rename = "protocol")]
28    pub protocol_version: String,
29
30    /// A string identifying the server or server family for diagnostic purposes.
31    pub server: Option<String>,
32
33    /// The server time at the time the request was received.
34    pub daystart: Option<DayStart>,
35
36    /// The applications to update.
37    ///
38    /// These are the 'app' children objects of the request object.
39    #[serde(rename = "app")]
40    pub apps: Vec<App>,
41}
42
43#[derive(Clone, Debug, Deserialize, PartialEq)]
44pub struct DayStart {
45    /// The number of calendar days that have elapsed since January 1st, 2007 in the server's
46    /// locale, at the time the request was received.
47    pub elapsed_days: Option<u32>,
48    /// The number of seconds since the most recent midnight of the server's locale, at the time
49    /// the request was received.
50    pub elapsed_seconds: Option<u32>,
51}
52
53#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
54pub struct App {
55    #[serde(rename = "appid")]
56    pub id: String,
57
58    /// The state of the product on the server.
59    pub status: OmahaStatus,
60
61    /// This holds the following fields of the app object:
62    ///   cohort
63    ///   cohorthint
64    ///   cohortname
65    #[serde(flatten)]
66    pub cohort: Cohort,
67
68    /// Optional ping, used for user counting.
69    pub ping: Option<Ping>,
70
71    /// Information about the update.
72    #[serde(rename = "updatecheck")]
73    pub update_check: Option<UpdateCheck>,
74
75    /// Any number of event status.
76    #[serde(rename = "event")]
77    pub events: Option<Vec<Event>>,
78
79    /// Optional attributes Omaha sends.
80    #[serde(flatten)]
81    pub extra_attributes: Map<String, Value>,
82}
83
84impl App {
85    pub fn get_manifest_version(&self) -> Option<String> {
86        self.update_check.as_ref().and_then(|update_check| {
87            update_check.manifest.as_ref().map(|manifest| manifest.version.clone())
88        })
89    }
90}
91
92#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
93#[serde(field_identifier, rename_all = "lowercase")]
94pub enum OmahaStatus {
95    #[default]
96    Ok,
97    /// The product is recognized, but due to policy restrictions the server must refuse to give a
98    /// meaningful response.
99    Restricted,
100    /// No update is available for this client at this time.
101    NoUpdate,
102    Error(String),
103}
104
105#[derive(Clone, Debug, Deserialize, PartialEq)]
106pub struct Ping {
107    /// Should be "ok".
108    status: OmahaStatus,
109}
110
111#[derive(Clone, Debug, Deserialize, PartialEq)]
112pub struct Event {
113    /// Should be "ok".
114    pub status: OmahaStatus,
115}
116
117#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
118pub struct UpdateCheck {
119    /// Whether there's an update available.
120    pub status: OmahaStatus,
121    /// More information about the status.
122    pub info: Option<String>,
123
124    /// The base URL of all the packages in this app.
125    pub urls: Option<URLs>,
126
127    /// The manifest about the update.
128    pub manifest: Option<Manifest>,
129
130    /// Possibly contains whether urgent_update is specified or realm_id.
131    #[serde(flatten)]
132    pub extra_attributes: Map<String, Value>,
133}
134
135impl UpdateCheck {
136    pub fn ok(urls: impl IntoIterator<Item = impl Into<String>>) -> Self {
137        UpdateCheck {
138            urls: Some(URLs::new(urls.into_iter().map(Into::into).collect())),
139            ..UpdateCheck::default()
140        }
141    }
142
143    pub fn no_update() -> Self {
144        UpdateCheck { status: OmahaStatus::NoUpdate, ..UpdateCheck::default() }
145    }
146
147    /// Returns an iterator of all url codebases in this `updatecheck`.
148    pub fn get_all_url_codebases(&self) -> impl Iterator<Item = &str> {
149        self.urls.iter().flat_map(|urls| &urls.url).map(|url| url.codebase.as_str())
150    }
151
152    /// Returns an iterator of all packages in this `updatecheck`.
153    pub fn get_all_packages(&self) -> impl Iterator<Item = &Package> {
154        self.manifest.iter().flat_map(|m| &m.packages.package)
155    }
156
157    /// Returns an iterator of all full urls in this `updatecheck`.
158    pub fn get_all_full_urls(&self) -> impl Iterator<Item = String> + '_ {
159        self.get_all_url_codebases().flat_map(move |codebase| {
160            self.get_all_packages().map(move |package| format!("{}{}", codebase, package.name))
161        })
162    }
163}
164
165/// Wrapper for a list of URL.
166#[derive(Clone, Debug, Deserialize, PartialEq)]
167pub struct URLs {
168    pub url: Vec<URL>,
169}
170
171impl URLs {
172    pub fn new(urls: Vec<String>) -> Self {
173        URLs { url: urls.into_iter().map(|url| URL { codebase: url }).collect() }
174    }
175}
176
177#[derive(Clone, Debug, Deserialize, PartialEq)]
178pub struct URL {
179    // The base URL of all the packages in this app.
180    pub codebase: String,
181}
182
183#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
184pub struct Manifest {
185    pub version: String,
186
187    pub actions: Actions,
188    pub packages: Packages,
189}
190
191/// Wrapper for a list of Action.
192#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
193pub struct Actions {
194    pub action: Vec<Action>,
195}
196
197#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
198pub struct Action {
199    /// The name of the event.
200    pub event: Option<String>,
201
202    /// The command to run.
203    pub run: Option<String>,
204
205    #[serde(flatten)]
206    pub extra_attributes: Map<String, Value>,
207}
208
209/// Wrapper for a list of Package.
210#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
211pub struct Packages {
212    pub package: Vec<Package>,
213}
214
215impl Packages {
216    pub fn new(package: Vec<Package>) -> Self {
217        Self { package }
218    }
219}
220
221#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
222pub struct Package {
223    /// Package name, append to the URL base to form a full URL.
224    pub name: String,
225    pub required: bool,
226    pub size: Option<u64>,
227    /// SHA1 of the package file encoded in base64.
228    pub hash: Option<String>,
229    /// SHA256 of the package file encoded in hex string.
230    pub hash_sha256: Option<String>,
231
232    /// The fingerprint of the package.
233    #[serde(rename = "fp")]
234    pub fingerprint: String,
235
236    #[serde(flatten)]
237    pub extra_attributes: Map<String, Value>,
238}
239
240impl Package {
241    pub fn with_name(name: impl Into<String>) -> Self {
242        Self { name: name.into(), ..Self::default() }
243    }
244}
245
246/// Parse a slice of bytes into a Response object (stripping out the ResponseWrapper in the process)
247pub fn parse_json_response(json: &[u8]) -> serde_json::Result<Response> {
248    #[derive(Deserialize)]
249    struct ResponseWrapper {
250        response: Response,
251    }
252
253    let wrapper: ResponseWrapper = parse_safe_json(json)?;
254    Ok(wrapper.response)
255}
256
257/// The returned JSON may use a strategy to mitigate against XSSI attacks by pre-pending the
258/// following string to the actual, valid, JSON:
259///
260/// ")]}'\n"
261///
262/// This function detects this case and has serde parse the valid json instead.
263fn parse_safe_json<'a, T>(raw: &'a [u8]) -> serde_json::Result<T>
264where
265    T: Deserialize<'a>,
266{
267    let safety_prefix = b")]}'\n";
268    // if the raw data starts with the safety prefix, adjust the slice to parse to be after the
269    // safety prefix.
270    if raw.starts_with(safety_prefix) {
271        serde_json::from_slice(&raw[safety_prefix.len()..])
272    } else {
273        serde_json::from_slice(raw)
274    }
275}