瀏覽代碼

Start implementing upload logic in an update module

timvisee 7 年之前
父節點
當前提交
fdb5a5a8ac
共有 11 個文件被更改,包括 180 次插入161 次删除
  1. 0 8
      Cargo.lock
  2. 0 2
      Cargo.toml
  3. 0 39
      src/action/upload.rs
  4. 5 5
      src/b64.rs
  5. 5 103
      src/main.rs
  6. 7 0
      src/send/key_set.rs
  7. 0 0
      src/send/metadata.rs
  8. 4 1
      src/send/mod.rs
  9. 3 0
      src/send/reader.rs
  10. 3 3
      src/send/send_file.rs
  11. 153 0
      src/send/upload.rs

+ 0 - 8
Cargo.lock

@@ -185,7 +185,6 @@ dependencies = [
  "clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "hkdf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.11.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -195,7 +194,6 @@ dependencies = [
  "serde_json 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "version-compare 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -988,11 +986,6 @@ name = "vec_map"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
-[[package]]
-name = "version-compare"
-version = "0.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "version_check"
 version = "0.1.3"
@@ -1155,7 +1148,6 @@ dependencies = [
 "checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
 "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
 "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
-"checksum version-compare 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "78068add8bf1e4d37d13fa5867182fe4c03f8e525c831053733f83aaba942d37"
 "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"

+ 0 - 2
Cargo.toml

@@ -9,7 +9,6 @@ chrono = "0.4"
 clap = "2.31"
 hkdf = "0.3"
 hyper = "0.11.9" # same as reqwest
-lazy_static = "1.0"
 mime_guess = "2.0.0-alpha.2"
 open = "1"
 openssl = "0.10"
@@ -19,4 +18,3 @@ serde_derive = "1.0"
 serde_json = "1.0"
 sha2 = "0.7"
 url = "1.7"
-version-compare = "0.0"

+ 0 - 39
src/action/upload.rs

@@ -1,39 +0,0 @@
-use super::super::url::Url;
-
-use super::super::send::file::File;
-
-/// The response from the server after a file has been uploaded.
-/// This response contains the file ID and owner key, to manage the file.
-///
-/// It also contains the download URL, although an additional secret is
-/// required.
-///
-/// The download URL can be generated using `download_url()` which will
-/// include the required secret in the URL.
-#[derive(Debug, Deserialize)]
-pub struct UploadResponse {
-    /// The file ID.
-    id: String,
-
-    /// The URL the file is reachable at.
-    /// This includes the file ID, but does not include the secret.
-    url: String,
-
-    /// The owner key, used to do further file modifications.
-    owner: String,
-}
-
-impl UploadResponse {
-    /// Convert this response into a file object.
-    ///
-    /// The `host` and `secret` must be given.
-    pub fn into_file(self, host: Url, secret: Vec<u8>) -> File {
-        File::new_now(
-            self.id,
-            host,
-            self.url,
-            secret,
-            self.owner,
-        )
-    }
-}

+ 5 - 5
src/b64.rs

@@ -6,14 +6,14 @@
 
 extern crate base64;
 
-use self::base64::DecodeError;
+// use self::base64::DecodeError;
 
 /// Encode the given byte slice using base64, in an URL-safe manner.
 pub fn encode(input: &[u8]) -> String {
     base64::encode_config(input, base64::URL_SAFE_NO_PAD)
 }
 
-/// Decode the given string as base64, in an URL-safe manner.
-pub fn decode(input: &str) -> Result<Vec<u8>, DecodeError> {
-    base64::decode_config(input, base64::URL_SAFE_NO_PAD)
-}
+// /// Decode the given string as base64, in an URL-safe manner.
+// pub fn decode(input: &str) -> Result<Vec<u8>, DecodeError> {
+//     base64::decode_config(input, base64::URL_SAFE_NO_PAD)
+// }

+ 5 - 103
src/main.rs

@@ -1,5 +1,4 @@
 extern crate hyper;
-extern crate lazy_static;
 extern crate mime_guess;
 extern crate open;
 extern crate openssl;
@@ -13,26 +12,11 @@ mod app;
 mod b64;
 mod cmd;
 mod crypto;
-mod metadata;
-mod reader;
 mod send;
 mod util;
 
-use std::fs::File;
-use std::io::BufReader;
-use std::path::Path;
-
-use openssl::symm::{Cipher, encrypt_aead};
-use reqwest::header::Authorization;
-use reqwest::mime::APPLICATION_OCTET_STREAM;
-use reqwest::multipart::Part;
-
-use action::upload::UploadResponse;
 use cmd::Handler;
 use cmd::cmd_upload::CmdUpload;
-use metadata::{Metadata, XFileMetadata};
-use reader::EncryptedFileReaderTagged;
-use send::key_set::KeySet;
 
 /// Application entrypoint.
 fn main() {
@@ -61,92 +45,10 @@ fn invoke_action(handler: &Handler) {
 
 /// The upload action.
 fn action_upload(cmd_upload: &CmdUpload) {
-    // Get the path and host
-    let path = Path::new(cmd_upload.file());
-    let host = cmd_upload.host();
-
-    // Make sure the path is a file
-    if !path.is_file() {
-        panic!("The selected path is not a file");
-    }
-
-    // TODO: a fixed path for now, as upload test
-    let file_ext = path.extension().unwrap().to_str().unwrap();
-    let file_name = path.file_name().unwrap().to_str().unwrap().to_owned();
-
-    // Create a new reqwest client
-    let client = reqwest::Client::new();
-
-    // Generate a key
-    let key = KeySet::generate(true);
-
-    // Guess the mimetype of the file
-    let file_mime = mime_guess::get_mime_type(file_ext);
-
-    // Construct the metadata
-    let metadata = Metadata::from(key.iv(), file_name.clone(), file_mime);
-
-    // Convert the metadata to JSON bytes
-    let metadata = metadata.to_json().into_bytes();
+    // // Get the path and host
+    // let path = Path::new(cmd_upload.file());
+    // let host = cmd_upload.host();
 
-    // Choose a file and meta cipher type
-    let cipher = Cipher::aes_128_gcm();
-
-    // Encrypt the metadata, and append the tag to it
-    let mut metadata_tag = vec![0u8; 16];
-    let mut metadata = encrypt_aead(
-        cipher,
-        key.meta_key().unwrap(),
-        Some(&[0u8; 12]),
-        &[],
-        &metadata,
-        &mut metadata_tag,
-    ).unwrap();
-    metadata.append(&mut metadata_tag);
-
-    // Open the file and create an encrypted file reader
-    let file = File::open(path).unwrap();
-    let reader = EncryptedFileReaderTagged::new(
-        file,
-        cipher,
-        key.file_key().unwrap(),
-        key.iv(),
-    ).unwrap();
-
-    // Buffer the encrypted reader, and determine the length
-    let reader_len = reader.len().unwrap();
-    let reader = BufReader::new(reader);
-
-    // Build the file part, configure the form to send
-    let part = Part::reader_with_length(reader, reader_len)
-        .file_name(file_name)
-        .mime(APPLICATION_OCTET_STREAM);
-    let form = reqwest::multipart::Form::new()
-        .part("data", part);
-
-    // Make the request
-    // TODO: properly format an URL here
-    let url = host.join("api/upload").expect("invalid host");
-    let mut res = client.post(url.as_str())
-        .header(Authorization(format!("send-v1 {}", key.auth_key_encoded().unwrap())))
-        .header(XFileMetadata::from(&metadata))
-        .multipart(form)
-        .send()
-        .unwrap();
-
-    // Parse the response
-    let upload_res: UploadResponse = res.json().unwrap();
-
-    // Print the response
-    let file = upload_res.into_file(host, key.secret().to_vec());
-    let url = file.download_url();
-    println!("File: {:#?}", file);
-    println!("Secret key: {}", key.secret_encoded());
-    println!("Download URL: {}", url);
-
-    // Open the URL in the browser
-    open::that(url).expect("failed to open URL");
+    // // Open the URL in the browser
+    // open::that(url).expect("failed to open URL");
 }
-
-// TODO: implement this some other way
-unsafe impl Send for EncryptedFileReaderTagged {}

+ 7 - 0
src/send/key_set.rs

@@ -1,3 +1,5 @@
+use super::super::openssl::symm::Cipher;
+
 use b64;
 use crypto::{derive_auth_key, derive_file_key, derive_meta_key, rand_bytes};
 
@@ -98,4 +100,9 @@ impl KeySet {
     pub fn meta_key(&self) -> Option<&Vec<u8>> {
         self.meta_key.as_ref()
     }
+
+    /// Get the cipher type to use in combination with these keys.
+    pub fn cipher() -> Cipher {
+        Cipher::aes_128_gcm()
+    }
 }

+ 0 - 0
src/metadata.rs → src/send/metadata.rs


+ 4 - 1
src/send/mod.rs

@@ -3,5 +3,8 @@
 //! This API client may be used to upload, download, modify or delete files
 //! to and from a secure Firefox Send server.
 
-pub mod file;
 pub mod key_set;
+pub mod metadata;
+pub mod reader;
+pub mod send_file;
+pub mod upload;

+ 3 - 0
src/reader.rs → src/send/reader.rs

@@ -215,3 +215,6 @@ impl Read for EncryptedFileReaderTagged {
         Ok(self.read(buf)? + total)
     }
 }
+
+// TODO: implement this some other way
+unsafe impl Send for EncryptedFileReaderTagged {}

+ 3 - 3
src/send/file.rs → src/send/send_file.rs

@@ -10,7 +10,7 @@ use super::super::b64;
 /// The struct contains the file ID, the file URL, the key that is required
 /// in combination with the file, and the owner key.
 #[derive(Debug)]
-pub struct File {
+pub struct SendFile {
     /// The ID of the file on that server.
     id: String,
 
@@ -30,7 +30,7 @@ pub struct File {
     owner_key: String,
 }
 
-impl File {
+impl SendFile {
     /// Construct a new file.
     pub fn new(
         id: String,
@@ -40,7 +40,7 @@ impl File {
         secret: Vec<u8>,
         owner_key: String,
     ) -> Self {
-        File {
+        Self {
             id,
             time,
             host,

+ 153 - 0
src/send/upload.rs

@@ -0,0 +1,153 @@
+use std::fs::File;
+use std::io::BufReader;
+use std::path::Path;
+
+use super::super::mime_guess::get_mime_type;
+use super::super::openssl::symm::encrypt_aead;
+use super::super::reqwest;
+use super::super::reqwest::header::Authorization;
+use super::super::reqwest::mime::APPLICATION_OCTET_STREAM;
+use super::super::reqwest::multipart::Part;
+use super::super::url::Url;
+
+use super::key_set::KeySet;
+use super::metadata::{Metadata, XFileMetadata};
+use super::reader::EncryptedFileReaderTagged;
+use super::send_file::SendFile;
+
+pub type Result<T> = ::std::result::Result<T, UploadError>;
+
+/// A file upload action to a Send server.
+pub struct Upload {
+    /// The Send host to upload the file to.
+    host: Url,
+
+    /// The file to upload.
+    path: Box<Path>,
+}
+
+impl Upload {
+    /// Construct a new upload action.
+    pub fn new(host: Url, path: Box<Path>) -> Self {
+        Self {
+            host,
+            path,
+        }
+    }
+
+    /// Invoke the upload action.
+    pub fn invoke(self) -> Result<SendFile> {
+        // Make sure the given path is a file
+        if !self.path.is_file() {
+            return Err(UploadError::NotAFile);
+        }
+
+        // Grab some file details
+        let file_ext = self.path.extension().unwrap().to_str().unwrap();
+        let file_name = self.path.file_name().unwrap().to_str().unwrap().to_owned();
+        let file_mime = get_mime_type(file_ext);
+
+        // Generate a key set
+        let key = KeySet::generate(true);
+
+        // Construct the metadata
+        let metadata = Metadata::from(key.iv(), file_name.clone(), file_mime)
+            .to_json()
+            .into_bytes();
+
+        // Encrypt the metadata, and append the tag to it
+        let mut metadata_tag = vec![0u8; 16];
+        let mut metadata = encrypt_aead(
+            KeySet::cipher(),
+            key.meta_key().unwrap(),
+            Some(&[0u8; 12]),
+            &[],
+            &metadata,
+            &mut metadata_tag,
+        ).unwrap();
+        metadata.append(&mut metadata_tag);
+
+        // Open the file and create an encrypted file reader
+        let file = File::open(&self.path).unwrap();
+        let reader = EncryptedFileReaderTagged::new(
+            file,
+            KeySet::cipher(),
+            key.file_key().unwrap(),
+            key.iv(),
+        ).unwrap();
+
+        // Buffer the encrypted reader, and determine the length
+        let reader_len = reader.len().unwrap();
+        let reader = BufReader::new(reader);
+
+        // Build the file part, configure the form to send
+        let part = Part::reader_with_length(reader, reader_len)
+            .file_name(file_name)
+            .mime(APPLICATION_OCTET_STREAM);
+        let form = reqwest::multipart::Form::new()
+            .part("data", part);
+
+        // Create a new reqwest client
+        let client = reqwest::Client::new();
+
+        // Make the request
+        // TODO: properly format an URL here
+        let url = self.host.join("api/upload").expect("invalid host");
+        let mut res = client.post(url.as_str())
+            .header(Authorization(format!("send-v1 {}", key.auth_key_encoded().unwrap())))
+            .header(XFileMetadata::from(&metadata))
+            .multipart(form)
+            .send()
+            .unwrap();
+
+        // Parse the response
+        let upload_res: UploadResponse = res.json().unwrap();
+
+        // Print the response
+        Ok(
+            upload_res.into_file(self.host, key.secret().to_vec())
+        )
+    }
+}
+
+pub enum UploadError {
+    /// The given file is not not an existing file.
+    /// Maybe it is a directory, or maybe it doesn't exist.
+    NotAFile,
+}
+
+/// The response from the server after a file has been uploaded.
+/// This response contains the file ID and owner key, to manage the file.
+///
+/// It also contains the download URL, although an additional secret is
+/// required.
+///
+/// The download URL can be generated using `download_url()` which will
+/// include the required secret in the URL.
+#[derive(Debug, Deserialize)]
+pub struct UploadResponse {
+    /// The file ID.
+    id: String,
+
+    /// The URL the file is reachable at.
+    /// This includes the file ID, but does not include the secret.
+    url: String,
+
+    /// The owner key, used to do further file modifications.
+    owner: String,
+}
+
+impl UploadResponse {
+    /// Convert this response into a file object.
+    ///
+    /// The `host` and `secret` must be given.
+    pub fn into_file(self, host: Url, secret: Vec<u8>) -> SendFile {
+        SendFile::new_now(
+            self.id,
+            host,
+            self.url,
+            secret,
+            self.owner,
+        )
+    }
+}