Browse Source

Extract methods in API download logic

timvisee 7 năm trước cách đây
mục cha
commit
268da48d9c
1 tập tin đã thay đổi với 142 bổ sung199 xóa
  1. 142 199
      api/src/action/download.rs

+ 142 - 199
api/src/action/download.rs

@@ -1,11 +1,15 @@
 use std::fs::File;
-use std::io;
+use std::io::{
+    self,
+    Read,
+};
 use std::sync::{Arc, Mutex};
 
 use openssl::symm::decrypt_aead;
 use reqwest::{
     Client, 
     Error as ReqwestError,
+    Response,
 };
 use reqwest::header::Authorization;
 use reqwest::header::ContentLength;
@@ -48,20 +52,39 @@ impl<'a> Download<'a> {
         // Create a key set for the file
         let mut key = KeySet::from(self.file);
 
-        // Build the meta cipher
-        // let mut metadata_tag = vec![0u8; 16];
-        // let mut meta_cipher = match encrypt_aead(
-        //     KeySet::cipher(),
-        //     self.meta_key().unwrap(),
-        //     self.iv,
-        //     &[],
-        //     &metadata,
-        //     &mut metadata_tag,
-        // ) {
-        //     Ok(cipher) => cipher,
-        //     Err(_) => // TODO: return error here,
-        // };
+        // Fetch the authentication nonce
+        let auth_nonce = self.fetch_auth_nonce(client);
+
+        // Fetch the meta nonce, set the input vector
+        let meta_nonce = self.fetch_meta_nonce(&client, &mut key, auth_nonce);
+
+        // Open the file we will write to
+        // TODO: this should become a temporary file first
+        let out = File::create("downloaded.zip")
+            .expect("failed to open file");
+
+        // Create the file reader for downloading
+        let (reader, len) = self.create_file_reader(&key, meta_nonce, &client);
+
+        // Create the file writer
+        let writer = self.create_file_writer(
+            out,
+            len,
+            &key,
+            reporter.clone(),
+        );
+
+        // Download the file
+        self.download(reader, writer, len, reporter);
+
+        // TODO: return the file path
+        // TODO: return the new remote state (does it still exist remote)
+
+        Ok(())
+    }
 
+    /// Fetch the authentication nonce for the file from the Send server.
+    fn fetch_auth_nonce(&self, client: &Client) -> Vec<u8> {
         // Get the download url, and parse the nonce
         // TODO: do not unwrap here, return error
         let download_url = self.file.download_url(false);
@@ -78,7 +101,7 @@ impl<'a> Download<'a> {
 
         // Get the authentication nonce
         // TODO: don't unwrap here, return an error
-        let nonce = b64::decode(
+        b64::decode(
             response.headers()
                 .get_raw(HEADER_AUTH_NONCE)
                 .expect("missing authenticate header") 
@@ -91,17 +114,49 @@ impl<'a> Download<'a> {
                 .skip(1)
                 .next()
                 .expect("missing authentication nonce")
-        ).expect("failed to decode authentication nonce");
+        ).expect("failed to decode authentication nonce")
+    }
 
-        // Compute the cryptographic signature
+    /// Fetch the metadata nonce.
+    /// This method also sets the input vector on the given key set,
+    /// extracted from the metadata.
+    ///
+    /// The key set, along with the authentication nonce must be given.
+    /// The meta nonce is returned.
+    fn fetch_meta_nonce(
+        &self,
+        client: &Client,
+        key: &mut KeySet,
+        auth_nonce: Vec<u8>,
+    ) -> Vec<u8> {
+        // Fetch the metadata and the nonce
+        let (metadata, meta_nonce) = self.fetch_metadata(client, key, auth_nonce);
+
+        // Set the input vector, and return the nonce
+        key.set_iv(metadata.iv());
+        meta_nonce
+    }
+
+    /// Create a metadata nonce, and fetch the metadata for the file from the
+    /// Send server.
+    ///
+    /// The key set, along with the authentication nonce must be given.
+    ///
+    /// The metadata, with the meta nonce is returned.
+    fn fetch_metadata(
+        &self,
+        client: &Client,
+        key: &KeySet,
+        auth_nonce: Vec<u8>,
+    ) -> (Metadata, Vec<u8>) {
+        // Compute the cryptographic signature for authentication
         // TODO: do not unwrap, return an error
-        let sig = signature_encoded(key.auth_key().unwrap(), &nonce)
+        let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce)
             .expect("failed to compute metadata signature");
 
-        // Get the meta URL, fetch the metadata
+        // Buidl the request, fetch the encrypted metadata
         // TODO: do not unwrap here, return error
-        let meta_url = self.file.api_meta_url();
-        let mut response = client.get(meta_url)
+        let mut response = client.get(self.file.api_meta_url())
             .header(Authorization(
                 format!("send-v1 {}", sig)
             ))
@@ -132,24 +187,36 @@ impl<'a> Download<'a> {
                 .expect("missing metadata nonce")
         ).expect("failed to decode metadata nonce");
 
-        // Parse the metadata response
-        let meta_response: MetadataResponse = response.json()
-            .expect("failed to parse metadata response");
-
-        // Decrypt the metadata, set the input vector
-        let metadata = meta_response.decrypt_metadata(&key)
-            .expect("failed to decrypt metadata");
-        key.set_iv(metadata.iv());
+        // Parse the metadata response, and decrypt it
+        (
+            response.json::<MetadataResponse>()
+                .expect("failed to parse metadata response")
+                .decrypt_metadata(&key)
+                .expect("failed to decrypt metadata"),
+            nonce,
+        )
+    }
 
+    /// Make a download request, and create a reader that downloads the
+    /// encrypted file.
+    ///
+    /// The response representing the file reader is returned along with the
+    /// length of the reader content.
+    fn create_file_reader(
+        &self,
+        key: &KeySet,
+        meta_nonce: Vec<u8>,
+        client: &Client,
+    ) -> (Response, u64) {
         // Compute the cryptographic signature
+        // TODO: use the metadata nonce here?
         // TODO: do not unwrap, return an error
-        let sig = signature_encoded(key.auth_key().unwrap(), &nonce)
+        let sig = signature_encoded(key.auth_key().unwrap(), &meta_nonce)
             .expect("failed to compute file signature");
 
-        // Get the download URL, build the download request
+        // Build and send the download request
         // TODO: do not unwrap here, return error
-        let download_url = self.file.api_download_url();
-        let mut response = client.get(download_url)
+        let response = client.get(self.file.api_download_url())
             .header(Authorization(
                 format!("send-v1 {}", sig)
             ))
@@ -164,32 +231,59 @@ impl<'a> Download<'a> {
         }
 
         // Get the content length
-        let response_len = response.headers().get::<ContentLength>()
+        // TODO: make sure there is enough disk space
+        let len = response.headers().get::<ContentLength>()
             .expect("failed to fetch file, missing content length header")
             .0;
 
-        // Open a file to write to, and build an encrypted writer
-        // TODO: this should become a temporary file first
-        let out = File::create("downloaded.zip")
-            .expect("failed to open file");
-        let writer = EncryptedFileWriter::new(
-            out,
-            response_len as usize,
-            KeySet::cipher(),
-            key.file_key().unwrap(),
-            key.iv(),
+        (response, len)
+    }
+
+    /// Create a file writer.
+    ///
+    /// This writer will will decrypt the input on the fly, and writes the
+    /// decrypted data to the given file.
+    fn create_file_writer(
+        &self,
+        file: File,
+        len: u64,
+        key: &KeySet,
+        reporter: Arc<Mutex<ProgressReporter>>,
+    ) -> ProgressWriter<EncryptedFileWriter> {
+        // Build an encrypted writer
+        let mut writer = ProgressWriter::new(
+            EncryptedFileWriter::new(
+                file,
+                len as usize,
+                KeySet::cipher(),
+                key.file_key().unwrap(),
+                key.iv(),
+            ).expect("failed to create encrypted writer")
         ).expect("failed to create encrypted writer");
-        let mut writer = ProgressWriter::new(writer)
-            .expect("failed to create encrypted writer");
+
+        // Set the reporter
         writer.set_reporter(reporter.clone());
 
+        writer
+    }
+
+    /// Download the file from the reader, and write it to the writer.
+    /// The length of the file must also be given.
+    /// The status will be reported to the given progress reporter.
+    fn download<R: Read>(
+        &self,
+        mut reader: R,
+        mut writer: ProgressWriter<EncryptedFileWriter>,
+        len: u64,
+        reporter: Arc<Mutex<ProgressReporter>>,
+    ) {
         // Start the writer
         reporter.lock()
             .expect("unable to start progress, failed to get lock")
-            .start(response_len);
+            .start(len);
 
         // Write to the output file
-        io::copy(&mut response, &mut writer)
+        io::copy(&mut reader, &mut writer)
             .expect("failed to download and decrypt file");
 
         // Finish
@@ -200,158 +294,7 @@ impl<'a> Download<'a> {
         // Verify the writer
         // TODO: delete the file if verification failed, show a proper error
         assert!(writer.unwrap().verified(), "downloaded and decrypted file could not be verified");
-
-        // // Crpate metadata and a file reader
-        // let metadata = self.create_metadata(&key, &file)?;
-        // let reader = self.create_reader(&key, reporter.clone())?;
-        // let reader_len = reader.len().unwrap();
-
-        // // Create the request to send
-        // let req = self.create_request(
-        //     client,
-        //     &key,
-        //     metadata,
-        //     reader,
-        // );
-
-        // // Start the reporter
-        // reporter.lock()
-        //     .expect("unable to start progress, failed to get lock")
-        //     .start(reader_len);
-
-        // // Execute the request
-        // let result = self.execute_request(req, client, &key);
-
-        // // Mark the reporter as finished
-        // reporter.lock()
-        //     .expect("unable to finish progress, failed to get lock")
-        //     .finish();
-        
-        // TODO: return the file path
-        // TODO: return the new remote state (does it still exist remote)
-
-        Ok(())
     }
-
-    // /// Create a blob of encrypted metadata.
-    // fn create_metadata(&self, key: &KeySet, file: &FileData)
-    //     -> Result<Vec<u8>>
-    // {
-    //     // Construct the metadata
-    //     let metadata = Metadata::from(
-    //         key.iv(),
-    //         file.name().to_owned(),
-    //         file.mime().clone(),
-    //     ).to_json().into_bytes();
-
-    //     // Encrypt the metadata
-    //     let mut metadata_tag = vec![0u8; 16];
-    //     let mut metadata = match encrypt_aead(
-    //         KeySet::cipher(),
-    //         key.meta_key().unwrap(),
-    //         Some(&[0u8; 12]),
-    //         &[],
-    //         &metadata,
-    //         &mut metadata_tag,
-    //     ) {
-    //         Ok(metadata) => metadata,
-    //         Err(_) => return Err(DownloadError::EncryptionError),
-    //     };
-
-    //     // Append the encryption tag
-    //     metadata.append(&mut metadata_tag);
-
-    //     Ok(metadata)
-    // }
-
-    // /// Create a reader that reads the file as encrypted stream.
-    // fn create_reader(
-    //     &self,
-    //     key: &KeySet,
-    //     reporter: Arc<Mutex<ProgressReporter>>,
-    // ) -> Result<EncryptedReader> {
-    //     // Open the file
-    //     let file = match File::open(self.path.as_path()) {
-    //         Ok(file) => file,
-    //         Err(_) => return Err(DownloadError::FileError),
-    //     };
-
-    //     // Create an encrypted reader
-    //     let reader = match EncryptedFileReaderTagged::new(
-    //         file,
-    //         KeySet::cipher(),
-    //         key.file_key().unwrap(),
-    //         key.iv(),
-    //     ) {
-    //         Ok(reader) => reader,
-    //         Err(_) => return Err(DownloadError::EncryptionError),
-    //     };
-
-    //     // Buffer the encrypted reader
-    //     let reader = BufReader::new(reader);
-
-    //     // Wrap into the encrypted reader
-    //     let mut reader = ProgressReader::new(reader)
-    //         .expect("failed to create progress reader");
-
-    //     // Initialize and attach the reporter
-    //     reader.set_reporter(reporter);
-
-    //     Ok(reader)
-    // }
-
-    // /// Build the request that will be send to the server.
-    // fn create_request(
-    //     &self,
-    //     client: &Client,
-    //     key: &KeySet,
-    //     metadata: Vec<u8>,
-    //     reader: EncryptedReader,
-    // ) -> Request {
-    //     // Get the reader length
-    //     let len = reader.len().expect("failed to get reader length");
-
-    //     // Configure a form to send
-    //     let part = Part::reader_with_length(reader, len)
-    //         // .file_name(file.name())
-    //         .mime(APPLICATION_OCTET_STREAM);
-    //     let form = Form::new()
-    //         .part("data", part);
-
-    //     // Define the URL to call
-    //     let url = self.host.join("api/upload").expect("invalid host");
-
-    //     // Build the request
-    //     client.post(url.as_str())
-    //         .header(Authorization(
-    //             format!("send-v1 {}", key.auth_key_encoded().unwrap())
-    //         ))
-    //         .header(XFileMetadata::from(&metadata))
-    //         .multipart(form)
-    //         .build()
-    //         .expect("failed to build an API request")
-    // }
-
-    // /// Execute the given request, and create a file object that represents the
-    // /// uploaded file.
-    // fn execute_request(&self, req: Request, client: &Client, key: &KeySet) 
-    //     -> Result<SendFile>
-    // {
-    //     // Execute the request
-    //     let mut res = match client.execute(req) {
-    //         Ok(res) => res,
-    //         Err(err) => return Err(DownloadError::RequestError(err)),
-    //     };
-
-    //     // Decode the response
-    //     let res: DownloadResponse = match res.json() {
-    //         Ok(res) => res,
-    //         Err(_) => return Err(DownloadError::DecodeError),
-    //     };
-
-    //     // Transform the responce into a file object
-    //     Ok(res.into_file(self.host.clone(), &key))
-    // }
 }
 
 /// Errors that may occur in the upload action.