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
88                .manifest
89                .as_ref()
90                .map(|manifest| manifest.version.clone())
91        })
92    }
93}
94
95#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
96#[serde(field_identifier, rename_all = "lowercase")]
97pub enum OmahaStatus {
98    #[default]
99    Ok,
100    /// The product is recognized, but due to policy restrictions the server must refuse to give a
101    /// meaningful response.
102    Restricted,
103    /// No update is available for this client at this time.
104    NoUpdate,
105    Error(String),
106}
107
108#[derive(Clone, Debug, Deserialize, PartialEq)]
109pub struct Ping {
110    /// Should be "ok".
111    status: OmahaStatus,
112}
113
114#[derive(Clone, Debug, Deserialize, PartialEq)]
115pub struct Event {
116    /// Should be "ok".
117    pub status: OmahaStatus,
118}
119
120#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
121pub struct UpdateCheck {
122    /// Whether there's an update available.
123    pub status: OmahaStatus,
124    /// More information about the status.
125    pub info: Option<String>,
126
127    /// The base URL of all the packages in this app.
128    pub urls: Option<URLs>,
129
130    /// The manifest about the update.
131    pub manifest: Option<Manifest>,
132
133    /// Possibly contains whether urgent_update is specified or realm_id.
134    #[serde(flatten)]
135    pub extra_attributes: Map<String, Value>,
136}
137
138impl UpdateCheck {
139    pub fn ok(urls: impl IntoIterator<Item = impl Into<String>>) -> Self {
140        UpdateCheck {
141            urls: Some(URLs::new(urls.into_iter().map(Into::into).collect())),
142            ..UpdateCheck::default()
143        }
144    }
145
146    pub fn no_update() -> Self {
147        UpdateCheck {
148            status: OmahaStatus::NoUpdate,
149            ..UpdateCheck::default()
150        }
151    }
152
153    /// Returns an iterator of all url codebases in this `updatecheck`.
154    pub fn get_all_url_codebases(&self) -> impl Iterator<Item = &str> {
155        self.urls
156            .iter()
157            .flat_map(|urls| &urls.url)
158            .map(|url| url.codebase.as_str())
159    }
160
161    /// Returns an iterator of all packages in this `updatecheck`.
162    pub fn get_all_packages(&self) -> impl Iterator<Item = &Package> {
163        self.manifest.iter().flat_map(|m| &m.packages.package)
164    }
165
166    /// Returns an iterator of all full urls in this `updatecheck`.
167    pub fn get_all_full_urls(&self) -> impl Iterator<Item = String> + '_ {
168        self.get_all_url_codebases().flat_map(move |codebase| {
169            self.get_all_packages()
170                .map(move |package| format!("{}{}", codebase, package.name))
171        })
172    }
173}
174
175/// Wrapper for a list of URL.
176#[derive(Clone, Debug, Deserialize, PartialEq)]
177pub struct URLs {
178    pub url: Vec<URL>,
179}
180
181impl URLs {
182    pub fn new(urls: Vec<String>) -> Self {
183        URLs {
184            url: urls.into_iter().map(|url| URL { codebase: url }).collect(),
185        }
186    }
187}
188
189#[derive(Clone, Debug, Deserialize, PartialEq)]
190pub struct URL {
191    // The base URL of all the packages in this app.
192    pub codebase: String,
193}
194
195#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
196pub struct Manifest {
197    pub version: String,
198
199    pub actions: Actions,
200    pub packages: Packages,
201}
202
203/// Wrapper for a list of Action.
204#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
205pub struct Actions {
206    pub action: Vec<Action>,
207}
208
209#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
210pub struct Action {
211    /// The name of the event.
212    pub event: Option<String>,
213
214    /// The command to run.
215    pub run: Option<String>,
216
217    #[serde(flatten)]
218    pub extra_attributes: Map<String, Value>,
219}
220
221/// Wrapper for a list of Package.
222#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
223pub struct Packages {
224    pub package: Vec<Package>,
225}
226
227impl Packages {
228    pub fn new(package: Vec<Package>) -> Self {
229        Self { package }
230    }
231}
232
233#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
234pub struct Package {
235    /// Package name, append to the URL base to form a full URL.
236    pub name: String,
237    pub required: bool,
238    pub size: Option<u64>,
239    /// SHA1 of the package file encoded in base64.
240    pub hash: Option<String>,
241    /// SHA256 of the package file encoded in hex string.
242    pub hash_sha256: Option<String>,
243
244    /// The fingerprint of the package.
245    #[serde(rename = "fp")]
246    pub fingerprint: String,
247
248    #[serde(flatten)]
249    pub extra_attributes: Map<String, Value>,
250}
251
252impl Package {
253    pub fn with_name(name: impl Into<String>) -> Self {
254        Self {
255            name: name.into(),
256            ..Self::default()
257        }
258    }
259}
260
261/// Parse a slice of bytes into a Response object (stripping out the ResponseWrapper in the process)
262pub fn parse_json_response(json: &[u8]) -> serde_json::Result<Response> {
263    #[derive(Deserialize)]
264    struct ResponseWrapper {
265        response: Response,
266    }
267
268    let wrapper: ResponseWrapper = parse_safe_json(json)?;
269    Ok(wrapper.response)
270}
271
272/// The returned JSON may use a strategy to mitigate against XSSI attacks by pre-pending the
273/// following string to the actual, valid, JSON:
274///
275/// ")]}'\n"
276///
277/// This function detects this case and has serde parse the valid json instead.
278fn parse_safe_json<'a, T>(raw: &'a [u8]) -> serde_json::Result<T>
279where
280    T: Deserialize<'a>,
281{
282    let safety_prefix = b")]}'\n";
283    // if the raw data starts with the safety prefix, adjust the slice to parse to be after the
284    // safety prefix.
285    if raw.starts_with(safety_prefix) {
286        serde_json::from_slice(&raw[safety_prefix.len()..])
287    } else {
288        serde_json::from_slice(raw)
289    }
290}