From 4231ec45cee74969d79c1007b7565fc12b77f20e Mon Sep 17 00:00:00 2001 From: Scott Lamb Date: Thu, 28 Oct 2021 12:38:29 -0700 Subject: [PATCH] start splitting up web.rs It's getting huge and hard to work with. The path stuff is easy to pull out. --- server/src/{web.rs => web/mod.rs} | 161 +--------------------------- server/src/web/path.rs | 169 ++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 157 deletions(-) rename server/src/{web.rs => web/mod.rs} (90%) create mode 100644 server/src/web/path.rs diff --git a/server/src/web.rs b/server/src/web/mod.rs similarity index 90% rename from server/src/web.rs rename to server/src/web/mod.rs index 5fb4e66..030b5ff 100644 --- a/server/src/web.rs +++ b/server/src/web/mod.rs @@ -1,7 +1,10 @@ // This file is part of Moonfire NVR, a security camera network video recorder. -// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. +// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. // SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. +mod path; + +use self::path::Path; use crate::body::Body; use crate::json; use crate::mp4; @@ -55,97 +58,6 @@ impl From for HttpError { } } -#[derive(Debug, Eq, PartialEq)] -enum Path { - TopLevel, // "/api/" - Request, // "/api/request" - InitSegment(i32, bool), // "/api/init/.mp4{.txt}" - Camera(Uuid), // "/api/cameras//" - Signals, // "/api/signals" - StreamRecordings(Uuid, db::StreamType), // "/api/cameras///recordings" - StreamViewMp4(Uuid, db::StreamType, bool), // "/api/cameras///view.mp4{.txt}" - StreamViewMp4Segment(Uuid, db::StreamType, bool), // "/api/cameras///view.m4s{.txt}" - StreamLiveMp4Segments(Uuid, db::StreamType), // "/api/cameras///live.m4s" - Login, // "/api/login" - Logout, // "/api/logout" - Static, // (anything that doesn't start with "/api/") - User(i32), // "/api/users/" - NotFound, -} - -impl Path { - fn decode(path: &str) -> Self { - let path = match path.strip_prefix("/api/") { - Some(p) => p, - None => return Path::Static, - }; - match path { - "" => return Path::TopLevel, - "login" => return Path::Login, - "logout" => return Path::Logout, - "request" => return Path::Request, - "signals" => return Path::Signals, - _ => {} - }; - if let Some(path) = path.strip_prefix("init/") { - let (debug, path) = match path.strip_suffix(".txt") { - Some(p) => (true, p), - None => (false, path), - }; - let path = match path.strip_suffix(".mp4") { - Some(p) => p, - None => return Path::NotFound, - }; - if let Ok(id) = i32::from_str(&path) { - return Path::InitSegment(id, debug); - } - return Path::NotFound; - } else if let Some(path) = path.strip_prefix("cameras/") { - let (uuid, path) = match path.split_once('/') { - Some(pair) => pair, - None => return Path::NotFound, - }; - - // TODO(slamb): require uuid to be in canonical format. - let uuid = match Uuid::parse_str(uuid) { - Ok(u) => u, - Err(_) => return Path::NotFound, - }; - - if path.is_empty() { - return Path::Camera(uuid); - } - - let (type_, path) = match path.split_once('/') { - Some(pair) => pair, - None => return Path::NotFound, - }; - let type_ = match db::StreamType::parse(type_) { - None => { - return Path::NotFound; - } - Some(t) => t, - }; - match path { - "recordings" => Path::StreamRecordings(uuid, type_), - "view.mp4" => Path::StreamViewMp4(uuid, type_, false), - "view.mp4.txt" => Path::StreamViewMp4(uuid, type_, true), - "view.m4s" => Path::StreamViewMp4Segment(uuid, type_, false), - "view.m4s.txt" => Path::StreamViewMp4Segment(uuid, type_, true), - "live.m4s" => Path::StreamLiveMp4Segments(uuid, type_), - _ => Path::NotFound, - } - } else if let Some(path) = path.strip_prefix("users/") { - if let Ok(id) = i32::from_str(path) { - return Path::User(id); - } - Path::NotFound - } else { - Path::NotFound - } - } -} - fn plain_response>(status: http::StatusCode, body: B) -> Response { Response::builder() .status(status) @@ -1505,71 +1417,6 @@ mod tests { } } - #[test] - fn paths() { - use super::Path; - use uuid::Uuid; - let cam_uuid = Uuid::parse_str("35144640-ff1e-4619-b0d5-4c74c185741c").unwrap(); - assert_eq!(Path::decode("/foo"), Path::Static); - assert_eq!(Path::decode("/api/"), Path::TopLevel); - assert_eq!( - Path::decode("/api/init/42.mp4"), - Path::InitSegment(42, false) - ); - assert_eq!( - Path::decode("/api/init/42.mp4.txt"), - Path::InitSegment(42, true) - ); - assert_eq!(Path::decode("/api/init/x.mp4"), Path::NotFound); // non-digit - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/"), - Path::Camera(cam_uuid) - ); - assert_eq!(Path::decode("/api/cameras/asdf/"), Path::NotFound); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/recordings"), - Path::StreamRecordings(cam_uuid, db::StreamType::Main) - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/sub/recordings"), - Path::StreamRecordings(cam_uuid, db::StreamType::Sub) - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/junk/recordings"), - Path::NotFound - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4"), - Path::StreamViewMp4(cam_uuid, db::StreamType::Main, false) - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4.txt"), - Path::StreamViewMp4(cam_uuid, db::StreamType::Main, true) - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s"), - Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, false) - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s.txt"), - Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, true) - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/live.m4s"), - Path::StreamLiveMp4Segments(cam_uuid, db::StreamType::Main) - ); - assert_eq!( - Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/junk"), - Path::NotFound - ); - assert_eq!(Path::decode("/api/login"), Path::Login); - assert_eq!(Path::decode("/api/logout"), Path::Logout); - assert_eq!(Path::decode("/api/signals"), Path::Signals); - assert_eq!(Path::decode("/api/junk"), Path::NotFound); - assert_eq!(Path::decode("/api/users/42"), Path::User(42)); - assert_eq!(Path::decode("/api/users/asdf"), Path::NotFound); - } - #[test] fn static_file() { testutil::init(); diff --git a/server/src/web/path.rs b/server/src/web/path.rs new file mode 100644 index 0000000..351c344 --- /dev/null +++ b/server/src/web/path.rs @@ -0,0 +1,169 @@ +// This file is part of Moonfire NVR, a security camera network video recorder. +// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. +// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. + +//! Decodes request paths. + +use std::str::FromStr; +use uuid::Uuid; + +/// A decoded request path. +#[derive(Debug, Eq, PartialEq)] +pub(super) enum Path { + TopLevel, // "/api/" + Request, // "/api/request" + InitSegment(i32, bool), // "/api/init/.mp4{.txt}" + Camera(Uuid), // "/api/cameras//" + Signals, // "/api/signals" + StreamRecordings(Uuid, db::StreamType), // "/api/cameras///recordings" + StreamViewMp4(Uuid, db::StreamType, bool), // "/api/cameras///view.mp4{.txt}" + StreamViewMp4Segment(Uuid, db::StreamType, bool), // "/api/cameras///view.m4s{.txt}" + StreamLiveMp4Segments(Uuid, db::StreamType), // "/api/cameras///live.m4s" + Login, // "/api/login" + Logout, // "/api/logout" + Static, // (anything that doesn't start with "/api/") + User(i32), // "/api/users/" + NotFound, +} + +impl Path { + /// Decodes a request path, notably not including any request parameters. + pub(super) fn decode(path: &str) -> Self { + let path = match path.strip_prefix("/api/") { + Some(p) => p, + None => return Path::Static, + }; + match path { + "" => return Path::TopLevel, + "login" => return Path::Login, + "logout" => return Path::Logout, + "request" => return Path::Request, + "signals" => return Path::Signals, + _ => {} + }; + if let Some(path) = path.strip_prefix("init/") { + let (debug, path) = match path.strip_suffix(".txt") { + Some(p) => (true, p), + None => (false, path), + }; + let path = match path.strip_suffix(".mp4") { + Some(p) => p, + None => return Path::NotFound, + }; + if let Ok(id) = i32::from_str(&path) { + return Path::InitSegment(id, debug); + } + return Path::NotFound; + } else if let Some(path) = path.strip_prefix("cameras/") { + let (uuid, path) = match path.split_once('/') { + Some(pair) => pair, + None => return Path::NotFound, + }; + + // TODO(slamb): require uuid to be in canonical format. + let uuid = match Uuid::parse_str(uuid) { + Ok(u) => u, + Err(_) => return Path::NotFound, + }; + + if path.is_empty() { + return Path::Camera(uuid); + } + + let (type_, path) = match path.split_once('/') { + Some(pair) => pair, + None => return Path::NotFound, + }; + let type_ = match db::StreamType::parse(type_) { + None => { + return Path::NotFound; + } + Some(t) => t, + }; + match path { + "recordings" => Path::StreamRecordings(uuid, type_), + "view.mp4" => Path::StreamViewMp4(uuid, type_, false), + "view.mp4.txt" => Path::StreamViewMp4(uuid, type_, true), + "view.m4s" => Path::StreamViewMp4Segment(uuid, type_, false), + "view.m4s.txt" => Path::StreamViewMp4Segment(uuid, type_, true), + "live.m4s" => Path::StreamLiveMp4Segments(uuid, type_), + _ => Path::NotFound, + } + } else if let Some(path) = path.strip_prefix("users/") { + if let Ok(id) = i32::from_str(path) { + return Path::User(id); + } + Path::NotFound + } else { + Path::NotFound + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn paths() { + use super::Path; + use uuid::Uuid; + let cam_uuid = Uuid::parse_str("35144640-ff1e-4619-b0d5-4c74c185741c").unwrap(); + assert_eq!(Path::decode("/foo"), Path::Static); + assert_eq!(Path::decode("/api/"), Path::TopLevel); + assert_eq!( + Path::decode("/api/init/42.mp4"), + Path::InitSegment(42, false) + ); + assert_eq!( + Path::decode("/api/init/42.mp4.txt"), + Path::InitSegment(42, true) + ); + assert_eq!(Path::decode("/api/init/x.mp4"), Path::NotFound); // non-digit + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/"), + Path::Camera(cam_uuid) + ); + assert_eq!(Path::decode("/api/cameras/asdf/"), Path::NotFound); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/recordings"), + Path::StreamRecordings(cam_uuid, db::StreamType::Main) + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/sub/recordings"), + Path::StreamRecordings(cam_uuid, db::StreamType::Sub) + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/junk/recordings"), + Path::NotFound + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4"), + Path::StreamViewMp4(cam_uuid, db::StreamType::Main, false) + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.mp4.txt"), + Path::StreamViewMp4(cam_uuid, db::StreamType::Main, true) + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s"), + Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, false) + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/view.m4s.txt"), + Path::StreamViewMp4Segment(cam_uuid, db::StreamType::Main, true) + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/live.m4s"), + Path::StreamLiveMp4Segments(cam_uuid, db::StreamType::Main) + ); + assert_eq!( + Path::decode("/api/cameras/35144640-ff1e-4619-b0d5-4c74c185741c/main/junk"), + Path::NotFound + ); + assert_eq!(Path::decode("/api/login"), Path::Login); + assert_eq!(Path::decode("/api/logout"), Path::Logout); + assert_eq!(Path::decode("/api/signals"), Path::Signals); + assert_eq!(Path::decode("/api/junk"), Path::NotFound); + assert_eq!(Path::decode("/api/users/42"), Path::User(42)); + assert_eq!(Path::decode("/api/users/asdf"), Path::NotFound); + } +}