remote_file.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. extern crate chrono;
  2. extern crate regex;
  3. use api::url::UrlBuilder;
  4. use url::{
  5. ParseError as UrlParseError,
  6. Url,
  7. };
  8. use self::chrono::{DateTime, Duration, Utc};
  9. use self::regex::Regex;
  10. use url_serde;
  11. use config::SEND_DEFAULT_EXPIRE_TIME;
  12. use crypto::b64;
  13. /// A pattern for share URL paths, capturing the file ID.
  14. // TODO: match any sub-path?
  15. // TODO: match URL-safe base64 chars for the file ID?
  16. // TODO: constrain the ID length?
  17. const SHARE_PATH_PATTERN: &'static str = r"^/?download/([[:alnum:]]{8,}={0,3})/?$";
  18. /// A pattern for share URL fragments, capturing the file secret.
  19. // TODO: constrain the secret length?
  20. const SHARE_FRAGMENT_PATTERN: &'static str = r"^([a-zA-Z0-9-_+/]+)?\s*$";
  21. /// A struct representing an uploaded file on a Send host.
  22. ///
  23. /// The struct contains the file ID, the file URL, the key that is required
  24. /// in combination with the file, and the owner key.
  25. #[derive(Clone, Debug, Serialize, Deserialize)]
  26. pub struct RemoteFile {
  27. /// The ID of the file on that server.
  28. id: String,
  29. /// The time the file was uploaded at, if known.
  30. upload_at: Option<DateTime<Utc>>,
  31. /// The time the file will expire at.
  32. expire_at: DateTime<Utc>,
  33. /// Define whether the expiry time is uncertain.
  34. expire_uncertain: bool,
  35. /// The host the file was uploaded to.
  36. #[serde(with = "url_serde")]
  37. host: Url,
  38. /// The file URL that was provided by the server.
  39. #[serde(with = "url_serde")]
  40. url: Url,
  41. /// The secret key that is required to download the file.
  42. secret: Vec<u8>,
  43. /// The owner key, that can be used to manage the file on the server.
  44. owner_token: Option<String>,
  45. }
  46. impl RemoteFile {
  47. /// Construct a new file.
  48. pub fn new(
  49. id: String,
  50. upload_at: Option<DateTime<Utc>>,
  51. expire_at: Option<DateTime<Utc>>,
  52. host: Url,
  53. url: Url,
  54. secret: Vec<u8>,
  55. owner_token: Option<String>,
  56. ) -> Self {
  57. // Assign the default expiry time if uncetain
  58. let expire_uncertain = expire_at.is_none();
  59. let expire_at = expire_at.unwrap_or(
  60. Utc::now() + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME)
  61. );
  62. // Build the object
  63. Self {
  64. id,
  65. upload_at,
  66. expire_at,
  67. expire_uncertain,
  68. host,
  69. url,
  70. secret,
  71. owner_token,
  72. }
  73. }
  74. /// Construct a new file, that was created at this exact time.
  75. /// This will set the file expiration time
  76. pub fn new_now(
  77. id: String,
  78. host: Url,
  79. url: Url,
  80. secret: Vec<u8>,
  81. owner_token: Option<String>,
  82. ) -> Self {
  83. // Get the current time
  84. let now = Utc::now();
  85. let expire_at = now + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME);
  86. // Construct and return
  87. Self::new(
  88. id,
  89. Some(now),
  90. Some(expire_at),
  91. host,
  92. url,
  93. secret,
  94. owner_token,
  95. )
  96. }
  97. /// Try to parse the given share URL.
  98. ///
  99. /// The given URL is matched against a share URL pattern,
  100. /// this does not check whether the host is a valid and online host.
  101. ///
  102. /// If the URL fragmet contains a file secret, it is also parsed.
  103. /// If it does not, the secret is left empty and must be specified
  104. /// manually.
  105. ///
  106. /// An optional owner token may be given.
  107. pub fn parse_url(url: Url, owner_token: Option<String>)
  108. -> Result<RemoteFile, FileParseError>
  109. {
  110. // Build the host
  111. let mut host = url.clone();
  112. host.set_fragment(None);
  113. host.set_query(None);
  114. host.set_path("");
  115. // Validate the path, get the file ID
  116. let re_path = Regex::new(SHARE_PATH_PATTERN).unwrap();
  117. let id = re_path.captures(url.path())
  118. .ok_or(FileParseError::InvalidUrl)?[1]
  119. .trim()
  120. .to_owned();
  121. // Get the file secret
  122. let mut secret = Vec::new();
  123. if let Some(fragment) = url.fragment() {
  124. let re_fragment = Regex::new(SHARE_FRAGMENT_PATTERN).unwrap();
  125. if let Some(raw) = re_fragment.captures(fragment)
  126. .ok_or(FileParseError::InvalidSecret)?
  127. .get(1)
  128. {
  129. secret = b64::decode(raw.as_str().trim())
  130. .map_err(|_| FileParseError::InvalidSecret)?
  131. }
  132. }
  133. // Construct the file
  134. Ok(Self::new(
  135. id,
  136. None,
  137. None,
  138. host,
  139. url,
  140. secret,
  141. owner_token,
  142. ))
  143. }
  144. /// Get the file ID.
  145. pub fn id(&self) -> &str {
  146. &self.id
  147. }
  148. /// Get the time the file will expire after.
  149. /// Note that this time may not be correct as it may have been guessed,
  150. /// see `expire_uncertain()`.
  151. pub fn expire_at(&self) -> DateTime<Utc> {
  152. self.expire_at
  153. }
  154. /// Get the duration the file will expire after.
  155. /// Note that this time may not be correct as it may have been guessed,
  156. /// see `expire_uncertain()`.
  157. pub fn expire_duration(&self) -> Duration {
  158. // Get the current time
  159. let now = Utc::now();
  160. // Return the duration if not expired, otherwise return zero
  161. if self.expire_at > now {
  162. self.expire_at - now
  163. } else {
  164. Duration::zero()
  165. }
  166. }
  167. /// Set the time this file will expire at.
  168. /// None may be given to assign the default expiry time with the
  169. /// uncertainty flag set.
  170. pub fn set_expire_at(&mut self, expire_at: Option<DateTime<Utc>>) {
  171. if let Some(expire_at) = expire_at {
  172. self.expire_at = expire_at;
  173. } else {
  174. self.expire_at = Utc::now() + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME);
  175. self.expire_uncertain = true;
  176. }
  177. }
  178. /// Set the time this file will expire at,
  179. /// based on the given duration from now.
  180. pub fn set_expire_duration(&mut self, duration: Duration) {
  181. self.set_expire_at(Some(Utc::now() + duration));
  182. }
  183. /// Check whether this file has expired, based on it's expiry property.
  184. pub fn has_expired(&self) -> bool {
  185. self.expire_at < Utc::now()
  186. }
  187. /// Check whehter the set expiry time is uncertain.
  188. /// If the expiry time of a file is unknown,
  189. /// the default time is assigned from the first time
  190. /// the file was used. Such time will be uncertain as it probably isn't
  191. /// correct.
  192. /// This time may be used however to check for expiry.
  193. pub fn expire_uncertain(&self) -> bool {
  194. self.expire_uncertain
  195. }
  196. /// Get the file URL, provided by the server.
  197. pub fn url(&self) -> &Url {
  198. &self.url
  199. }
  200. /// Get the raw secret.
  201. // TODO: ensure whether the secret is set?
  202. pub fn secret_raw(&self) -> &Vec<u8> {
  203. &self.secret
  204. }
  205. /// Get the secret as base64 encoded string.
  206. pub fn secret(&self) -> String {
  207. b64::encode(self.secret_raw())
  208. }
  209. /// Set the secret for this file.
  210. pub fn set_secret(&mut self, secret: Vec<u8>) {
  211. self.secret = secret;
  212. }
  213. /// Check whether a file secret is set.
  214. /// This secret must be set to decrypt a downloaded Send file.
  215. pub fn has_secret(&self) -> bool {
  216. !self.secret.is_empty()
  217. }
  218. /// Get the owner token if set.
  219. pub fn owner_token(&self) -> Option<&String> {
  220. self.owner_token.as_ref()
  221. }
  222. /// Get the owner token if set.
  223. pub fn owner_token_mut(&mut self) -> &mut Option<String> {
  224. &mut self.owner_token
  225. }
  226. /// Set the owner token, wrapped in an option.
  227. /// If `None` is given, the owner token will be unset.
  228. pub fn set_owner_token(&mut self, token: Option<String>) {
  229. self.owner_token = token;
  230. }
  231. /// Check whether an owner token is set in this remote file.
  232. pub fn has_owner_token(&self) -> bool {
  233. self.owner_token
  234. .clone()
  235. .map(|t| !t.is_empty())
  236. .unwrap_or(false)
  237. }
  238. /// Get the host URL for this remote file.
  239. pub fn host(&self) -> Url {
  240. self.host.clone()
  241. }
  242. /// Build the download URL of the given file.
  243. /// This URL is identical to the share URL, a term used in this API.
  244. /// Set `secret` to `true`, to include it in the URL if known.
  245. pub fn download_url(&self, secret: bool) -> Url {
  246. UrlBuilder::download(&self, secret)
  247. }
  248. /// Merge properties non-existant into this file, from the given other file.
  249. /// This is ofcourse only done for properties that may be empty.
  250. ///
  251. /// The file IDs are not asserted for equality.
  252. pub fn merge(&mut self, other: &RemoteFile, overwrite: bool) -> bool {
  253. // Remember whether anything was changed
  254. let mut changed = false;
  255. // Set the upload time
  256. if other.upload_at.is_some() && (self.upload_at.is_none() || overwrite) {
  257. self.upload_at = other.upload_at.clone();
  258. changed = true;
  259. }
  260. // Set the expire time
  261. if !other.expire_uncertain() && (self.expire_uncertain() || overwrite) {
  262. self.expire_at = other.expire_at.clone();
  263. self.expire_uncertain = other.expire_uncertain();
  264. changed = true;
  265. }
  266. // Set the secret
  267. if other.has_secret() && (!self.has_secret() || overwrite) {
  268. self.secret = other.secret_raw().clone();
  269. changed = true;
  270. }
  271. // Set the owner token
  272. if other.owner_token.is_some() && (self.owner_token.is_none() || overwrite) {
  273. self.owner_token = other.owner_token.clone();
  274. changed = true;
  275. }
  276. return changed;
  277. }
  278. }
  279. #[derive(Debug, Fail)]
  280. pub enum FileParseError {
  281. /// An URL format error.
  282. #[fail(display = "failed to parse remote file, invalid URL format")]
  283. UrlFormatError(#[cause] UrlParseError),
  284. /// An error for an invalid share URL format.
  285. #[fail(display = "failed to parse remote file, invalid URL")]
  286. InvalidUrl,
  287. /// An error for an invalid secret format, if an URL fragmet exists.
  288. #[fail(display = "failed to parse remote file, invalid secret in URL")]
  289. InvalidSecret,
  290. }