Bläddra i källkod

Extract nonce parsing logic to a module

timvisee 7 år sedan
förälder
incheckning
718238b35e

+ 1 - 0
IDEAS.md

@@ -38,3 +38,4 @@
 - Read and write files from and to stdin and stdout with `-` as file
 - Ask to add MIME extension to downloaded files without one on Windows
 - Fetch max file size from `server/jsconfig.js`
+- Define a redirect policy (allow setting max redirects)

+ 9 - 72
api/src/action/delete.rs

@@ -1,18 +1,13 @@
-// TODO: define redirect policy
-
 use reqwest::{Client, StatusCode};
 
 use api::data::{
     Error as DataError,
     OwnedData,
 };
-use crypto::b64;
+use api::nonce::{NonceError, request_auth_nonce};
 use ext::status_code::StatusCodeExt;
 use file::remote_file::RemoteFile;
 
-/// The name of the header that is used for the authentication nonce.
-const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
-
 /// An action to delete a remote file.
 pub struct Delete<'a> {
     /// The remote file to delete.
@@ -48,40 +43,13 @@ impl<'a> Delete<'a> {
     }
 
     /// Fetch the authentication nonce for the file from the remote server.
-    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, AuthError> {
-        // Get the download URL, and parse the nonce
-        let download_url = self.file.download_url(false);
-        let response = client.get(download_url)
-            .send()
-            .map_err(|_| AuthError::NonceReq)?;
-
-        // Validate the status code
-        let status = response.status();
-        if !status.is_success() {
-            // TODO: should we check here whether a 404 is returned?
-            // // Handle expired files
-            // if status == FILE_EXPIRED_STATUS {
-            //     return Err(Error::Expired);
-            // } else {
-            return Err(AuthError::NonceReqStatus(status, status.err_text()).into());
-            // }
-        }
-
-        // Get the authentication nonce
-        b64::decode(
-            response.headers()
-                .get_raw(HEADER_AUTH_NONCE)
-                .ok_or(AuthError::NoNonceHeader)?
-                .one()
-                .ok_or(AuthError::MalformedNonce)
-                .and_then(|line| String::from_utf8(line.to_vec())
-                    .map_err(|_| AuthError::MalformedNonce)
-                )?
-                .split_terminator(" ")
-                .skip(1)
-                .next()
-                .ok_or(AuthError::MalformedNonce)?
-        ).map_err(|_| AuthError::MalformedNonce.into())
+    fn fetch_auth_nonce(&self, client: &Client)
+        -> Result<Vec<u8>, PrepareError>
+    {
+        request_auth_nonce(
+            client,
+            self.file.download_url(false),
+        ).map_err(|err| PrepareError::Auth(err))
     }
 
     /// Send a request to delete the remote file, with the given data.
@@ -142,12 +110,6 @@ impl From<PrepareError> for Error {
     }
 }
 
-impl From<AuthError> for Error {
-    fn from(err: AuthError) -> Error {
-        PrepareError::Auth(err).into()
-    }
-}
-
 impl From<DeleteError> for Error {
     fn from(err: DeleteError) -> Error {
         Error::Delete(err)
@@ -167,7 +129,7 @@ pub enum DeleteDataError {
 pub enum PrepareError {
     /// Failed to authenticate
     #[fail(display = "Failed to authenticate")]
-    Auth(#[cause] AuthError),
+    Auth(#[cause] NonceError),
 
     /// An error occurred while building the deletion data that will be
     /// send to the server.
@@ -181,31 +143,6 @@ impl From<DataError> for PrepareError {
     }
 }
 
-#[derive(Fail, Debug)]
-pub enum AuthError {
-    /// Sending the request to gather the authentication encryption nonce
-    /// failed.
-    #[fail(display = "Failed to request authentication nonce")]
-    NonceReq,
-
-    /// The response for fetching the authentication encryption nonce
-    /// indicated an error and wasn't successful.
-    #[fail(display = "Bad HTTP response '{}' while requesting authentication nonce", _1)]
-    NonceReqStatus(StatusCode, String),
-
-    /// No authentication encryption nonce was included in the response
-    /// from the server, it was missing.
-    #[fail(display = "Missing authentication nonce in server response")]
-    NoNonceHeader,
-
-    /// The authentication encryption nonce from the response malformed or
-    /// empty.
-    /// Maybe the server responded with a new format that isn't supported yet
-    /// by this client.
-    #[fail(display = "Received malformed authentication nonce")]
-    MalformedNonce,
-}
-
 #[derive(Fail, Debug)]
 pub enum DeleteError {
     /// Sending the file deletion request failed.

+ 0 - 2
api/src/action/download.rs

@@ -1,5 +1,3 @@
-// TODO: define redirect policy
-
 use std::fs::File;
 use std::io::{
     self,

+ 0 - 2
api/src/action/exists.rs

@@ -1,5 +1,3 @@
-// TODO: define redirect policy
-
 use reqwest::{Client, StatusCode};
 
 use ext::status_code::StatusCodeExt;

+ 9 - 72
api/src/action/info.rs

@@ -1,5 +1,3 @@
-// TODO: define redirect policy
-
 use std::cmp::max;
 
 use reqwest::{
@@ -12,13 +10,10 @@ use api::data::{
     Error as DataError,
     OwnedData,
 };
-use crypto::b64;
+use api::nonce::{NonceError, request_auth_nonce};
 use ext::status_code::StatusCodeExt;
 use file::remote_file::RemoteFile;
 
-/// The name of the header that is used for the authentication nonce.
-const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
-
 /// An action to fetch info of a shared file.
 pub struct Info<'a> {
     /// The remote file to fetch the info for.
@@ -54,40 +49,13 @@ impl<'a> Info<'a> {
     }
 
     /// Fetch the authentication nonce for the file from the remote server.
-    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, AuthError> {
-        // Get the download URL, and parse the nonce
-        let download_url = self.file.download_url(false);
-        let response = client.get(download_url)
-            .send()
-            .map_err(|_| AuthError::NonceReq)?;
-
-        // Validate the status code
-        let status = response.status();
-        if !status.is_success() {
-            // TODO: should we check here whether a 404 is returned?
-            // // Handle expired files
-            // if status == FILE_EXPIRED_STATUS {
-            //     return Err(Error::Expired);
-            // } else {
-            return Err(AuthError::NonceReqStatus(status, status.err_text()).into());
-            // }
-        }
-
-        // Get the authentication nonce
-        b64::decode(
-            response.headers()
-                .get_raw(HEADER_AUTH_NONCE)
-                .ok_or(AuthError::NoNonceHeader)?
-                .one()
-                .ok_or(AuthError::MalformedNonce)
-                .and_then(|line| String::from_utf8(line.to_vec())
-                    .map_err(|_| AuthError::MalformedNonce)
-                )?
-                .split_terminator(" ")
-                .skip(1)
-                .next()
-                .ok_or(AuthError::MalformedNonce)?
-        ).map_err(|_| AuthError::MalformedNonce.into())
+    fn fetch_auth_nonce(&self, client: &Client)
+        -> Result<Vec<u8>, PrepareError>
+    {
+        request_auth_nonce(
+            client,
+            self.file.download_url(false),
+        ).map_err(|err| PrepareError::Auth(err))
     }
 
     /// Send the request for fetching the remote file info.
@@ -193,12 +161,6 @@ impl From<PrepareError> for Error {
     }
 }
 
-impl From<AuthError> for Error {
-    fn from(err: AuthError) -> Error {
-        PrepareError::Auth(err).into()
-    }
-}
-
 impl From<InfoError> for Error {
     fn from(err: InfoError) -> Error {
         Error::Info(err)
@@ -218,7 +180,7 @@ pub enum InfoDataError {
 pub enum PrepareError {
     /// Failed authenticating, needed to fetch the info
     #[fail(display = "Failed to authenticate")]
-    Auth(#[cause] AuthError),
+    Auth(#[cause] NonceError),
 
     /// An error occurred while building the info data that will be
     /// send to the server.
@@ -232,31 +194,6 @@ impl From<DataError> for PrepareError {
     }
 }
 
-#[derive(Fail, Debug)]
-pub enum AuthError {
-    /// Sending the request to gather the authentication encryption nonce
-    /// failed.
-    #[fail(display = "Failed to request authentication nonce")]
-    NonceReq,
-
-    /// The response for fetching the authentication encryption nonce
-    /// indicated an error and wasn't successful.
-    #[fail(display = "Bad HTTP response '{}' while requesting authentication nonce", _1)]
-    NonceReqStatus(StatusCode, String),
-
-    /// No authentication encryption nonce was included in the response
-    /// from the server, it was missing.
-    #[fail(display = "Missing authentication nonce in server response")]
-    NoNonceHeader,
-
-    /// The authentication encryption nonce from the response malformed or
-    /// empty.
-    /// Maybe the server responded with a new format that isn't supported yet
-    /// by this client.
-    #[fail(display = "Received malformed authentication nonce")]
-    MalformedNonce,
-}
-
 #[derive(Fail, Debug)]
 pub enum InfoError {
     /// Sending the request to fetch the file info failed.

+ 21 - 92
api/src/action/metadata.rs

@@ -1,11 +1,15 @@
-// TODO: define redirect policy
-
 use failure::Error as FailureError;
 use openssl::symm::decrypt_aead;
 use reqwest::{Client, StatusCode};
 use reqwest::header::Authorization;
 use serde_json;
 
+use api::nonce::{
+    HEADER_NONCE,
+    header_nonce,
+    NonceError,
+    request_auth_nonce,
+};
 use crypto::b64;
 use crypto::key_set::KeySet;
 use crypto::sig::signature_encoded;
@@ -13,9 +17,6 @@ use ext::status_code::StatusCodeExt;
 use file::metadata::Metadata as MetadataData;
 use file::remote_file::RemoteFile;
 
-/// The name of the header that is used for the authentication nonce.
-const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
-
 /// The HTTP status code that is returned for expired files.
 const FILE_EXPIRED_STATUS: StatusCode = StatusCode::NotFound;
 
@@ -50,42 +51,14 @@ impl<'a> Metadata<'a> {
             .map_err(|err| err.into())
     }
 
-    /// Fetch the authentication nonce for the file from the Send server.
+    /// Fetch the authentication nonce for the file from the remote server.
     fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, Error>
+        -> Result<Vec<u8>, RequestError>
     {
-        // Get the download url, and parse the nonce
-        let download_url = self.file.download_url(false);
-        let response = client.get(download_url)
-            .send()
-            .map_err(|_| AuthError::NonceReq)?;
-
-        // Validate the status code
-        let status = response.status();
-        if !status.is_success() {
-            // Handle expired files
-            if status == FILE_EXPIRED_STATUS {
-                return Err(Error::Expired);
-            } else {
-                return Err(AuthError::NonceReqStatus(status, status.err_text()).into());
-            }
-        }
-
-        // Get the authentication nonce
-        b64::decode(
-            response.headers()
-                .get_raw(HEADER_AUTH_NONCE)
-                .ok_or(AuthError::NoNonceHeader)?
-                .one()
-                .ok_or(AuthError::MalformedNonce)
-                .and_then(|line| String::from_utf8(line.to_vec())
-                    .map_err(|_| AuthError::MalformedNonce)
-                )?
-                .split_terminator(" ")
-                .skip(1)
-                .next()
-                .ok_or(AuthError::MalformedNonce)?
-        ).map_err(|_| AuthError::MalformedNonce.into())
+        request_auth_nonce(
+            client,
+            self.file.download_url(false),
+        ).map_err(|err| RequestError::Auth(err))
     }
 
     /// Create a metadata nonce, and fetch the metadata for the file from the
@@ -119,20 +92,8 @@ impl<'a> Metadata<'a> {
         }
 
         // Get the metadata nonce
-        let nonce = b64::decode(
-            response.headers()
-                .get_raw(HEADER_AUTH_NONCE)
-                .ok_or(MetaError::NoNonceHeader)?
-                .one()
-                .ok_or(MetaError::MalformedNonce)
-                .and_then(|line| String::from_utf8(line.to_vec())
-                    .map_err(|_| MetaError::MalformedNonce)
-                )?
-                .split_terminator(" ")
-                .skip(1)
-                .next()
-                .ok_or(MetaError::MalformedNonce)?
-        ).map_err(|_| MetaError::MalformedNonce)?;
+        let nonce = header_nonce(HEADER_NONCE, &response)
+            .map_err(|err| MetaError::Nonce(err))?;
 
         // Parse the metadata response, and decrypt it
         Ok(MetadataResponse::from(
@@ -228,9 +189,9 @@ pub enum Error {
     Expired,
 }
 
-impl From<AuthError> for Error {
-    fn from(err: AuthError) -> Error {
-        Error::Request(RequestError::Auth(err))
+impl From<RequestError> for Error {
+    fn from(err: RequestError) -> Error {
+        Error::Request(err)
     }
 }
 
@@ -244,38 +205,13 @@ impl From<MetaError> for Error {
 pub enum RequestError {
     /// Failed authenticating, in order to fetch the file data.
     #[fail(display = "Failed to authenticate")]
-    Auth(#[cause] AuthError),
+    Auth(#[cause] NonceError),
 
     /// Failed to retrieve the file metadata.
     #[fail(display = "Failed to retrieve file metadata")]
     Meta(#[cause] MetaError),
 }
 
-#[derive(Fail, Debug)]
-pub enum AuthError {
-    /// Sending the request to gather the authentication encryption nonce
-    /// failed.
-    #[fail(display = "Failed to request authentication nonce")]
-    NonceReq,
-
-    /// The response for fetching the authentication encryption nonce
-    /// indicated an error and wasn't successful.
-    #[fail(display = "Bad HTTP response '{}' while requesting authentication nonce", _1)]
-    NonceReqStatus(StatusCode, String),
-
-    /// No authentication encryption nonce was included in the response
-    /// from the server, it was missing.
-    #[fail(display = "Missing authentication nonce in server response")]
-    NoNonceHeader,
-
-    /// The authentication encryption nonce from the response malformed or
-    /// empty.
-    /// Maybe the server responded with a new format that isn't supported yet
-    /// by this client.
-    #[fail(display = "Received malformed authentication nonce")]
-    MalformedNonce,
-}
-
 #[derive(Fail, Debug)]
 pub enum MetaError {
     /// An error occurred while computing the cryptographic signature used for
@@ -292,16 +228,9 @@ pub enum MetaError {
     #[fail(display = "Bad HTTP response '{}' while requesting metadata nonce", _1)]
     NonceReqStatus(StatusCode, String),
 
-    /// No metadata encryption nonce was included in the response from the
-    /// server, it was missing.
-    #[fail(display = "Missing metadata nonce in server response")]
-    NoNonceHeader,
-
-    /// The metadata encryption nonce from the response malformed or empty.
-    /// Maybe the server responded with a new format that isn't supported yet
-    /// by this client.
-    #[fail(display = "Received malformed metadata nonce")]
-    MalformedNonce,
+    /// Couldn't parse the metadata encryption nonce.
+    #[fail(display = "Failed to parse the metadata encryption nonce")]
+    Nonce(#[cause] NonceError),
 
     /// The received metadata is malformed, and couldn't be decoded or
     /// interpreted.

+ 8 - 73
api/src/action/params.rs

@@ -1,18 +1,13 @@
-// TODO: define redirect policy
-
 use reqwest::{Client, StatusCode};
 
 use api::data::{
     Error as DataError,
     OwnedData,
 };
-use crypto::b64;
+use api::nonce::{NonceError, request_auth_nonce};
 use ext::status_code::StatusCodeExt;
 use file::remote_file::RemoteFile;
 
-/// The name of the header that is used for the authentication nonce.
-const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
-
 /// The default download count.
 pub const PARAMS_DEFAULT_DOWNLOAD: u8 = 1;
 pub const PARAMS_DEFAULT_DOWNLOAD_STR: &'static str = "1";
@@ -69,43 +64,14 @@ impl<'a> Params<'a> {
             .map_err(|err| err.into())
     }
 
-    /// Fetch the authentication nonce for the file from the Send server.
+    /// Fetch the authentication nonce for the file from the remote server.
     fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, AuthError>
+        -> Result<Vec<u8>, PrepareError>
     {
-        // Get the download URL, and parse the nonce
-        let download_url = self.file.download_url(false);
-        let response = client.get(download_url)
-            .send()
-            .map_err(|_| AuthError::NonceReq)?;
-
-        // Validate the status code
-        let status = response.status();
-        if !status.is_success() {
-            // TODO: should we check here whether a 404 is returned?
-            // // Handle expired files
-            // if status == FILE_EXPIRED_STATUS {
-            //     return Err(Error::Expired);
-            // } else {
-            return Err(AuthError::NonceReqStatus(status, status.err_text()).into());
-            // }
-        }
-
-        // Get the authentication nonce
-        b64::decode(
-            response.headers()
-                .get_raw(HEADER_AUTH_NONCE)
-                .ok_or(AuthError::NoNonceHeader)?
-                .one()
-                .ok_or(AuthError::MalformedNonce)
-                .and_then(|line| String::from_utf8(line.to_vec())
-                    .map_err(|_| AuthError::MalformedNonce)
-                )?
-                .split_terminator(" ")
-                .skip(1)
-                .next()
-                .ok_or(AuthError::MalformedNonce)?
-        ).map_err(|_| AuthError::MalformedNonce.into())
+        request_auth_nonce(
+            client,
+            self.file.download_url(false),
+        ).map_err(|err| PrepareError::Auth(err))
     }
 
     /// Send the request for changing the parameters.
@@ -219,12 +185,6 @@ impl From<PrepareError> for Error {
     }
 }
 
-impl From<AuthError> for Error {
-    fn from(err: AuthError) -> Error {
-        PrepareError::Auth(err).into()
-    }
-}
-
 impl From<ChangeError> for Error {
     fn from(err: ChangeError) -> Error {
         Error::Change(err)
@@ -250,7 +210,7 @@ pub enum ParamsDataError {
 pub enum PrepareError {
     /// Failed authenticating, needed to change the parameters.
     #[fail(display = "Failed to authenticate")]
-    Auth(#[cause] AuthError),
+    Auth(#[cause] NonceError),
 
     /// An error occurred while building the parameter data that will be send
     /// to the server.
@@ -264,31 +224,6 @@ impl From<DataError> for PrepareError {
     }
 }
 
-#[derive(Fail, Debug)]
-pub enum AuthError {
-    /// Sending the request to gather the authentication encryption nonce
-    /// failed.
-    #[fail(display = "Failed to request authentication nonce")]
-    NonceReq,
-
-    /// The response for fetching the authentication encryption nonce
-    /// indicated an error and wasn't successful.
-    #[fail(display = "Bad HTTP response '{}' while requesting authentication nonce", _1)]
-    NonceReqStatus(StatusCode, String),
-
-    /// No authentication encryption nonce was included in the response
-    /// from the server, it was missing.
-    #[fail(display = "Missing authentication nonce in server response")]
-    NoNonceHeader,
-
-    /// The authentication encryption nonce from the response malformed or
-    /// empty.
-    /// Maybe the server responded with a new format that isn't supported yet
-    /// by this client.
-    #[fail(display = "Received malformed authentication nonce")]
-    MalformedNonce,
-}
-
 #[derive(Fail, Debug)]
 pub enum ChangeError {
     /// Sending the request to change the parameters failed.

+ 7 - 72
api/src/action/password.rs

@@ -1,19 +1,14 @@
-// TODO: define redirect policy
-
 use reqwest::{Client, StatusCode};
 
 use api::data::{
     Error as DataError,
     OwnedData,
 };
-use crypto::b64;
+use api::nonce::{NonceError, request_auth_nonce};
 use crypto::key_set::KeySet;
 use ext::status_code::StatusCodeExt;
 use file::remote_file::RemoteFile;
 
-/// The name of the header that is used for the authentication nonce.
-const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
-
 /// An action to change a password of an uploaded Send file.
 pub struct Password<'a> {
     /// The remote file to change the password for.
@@ -65,41 +60,12 @@ impl<'a> Password<'a> {
 
     /// Fetch the authentication nonce for the file from the Send server.
     fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, AuthError>
+        -> Result<Vec<u8>, PrepareError>
     {
-        // Get the download URL, and parse the nonce
-        let download_url = self.file.download_url(false);
-        let response = client.get(download_url)
-            .send()
-            .map_err(|_| AuthError::NonceReq)?;
-
-        // Validate the status code
-        let status = response.status();
-        if !status.is_success() {
-            // TODO: should we check here whether a 404 is returned?
-            // // Handle expired files
-            // if status == FILE_EXPIRED_STATUS {
-            //     return Err(Error::Expired);
-            // } else {
-            return Err(AuthError::NonceReqStatus(status, status.err_text()).into());
-            // }
-        }
-
-        // Get the authentication nonce
-        b64::decode(
-            response.headers()
-                .get_raw(HEADER_AUTH_NONCE)
-                .ok_or(AuthError::NoNonceHeader)?
-                .one()
-                .ok_or(AuthError::MalformedNonce)
-                .and_then(|line| String::from_utf8(line.to_vec())
-                    .map_err(|_| AuthError::MalformedNonce)
-                )?
-                .split_terminator(" ")
-                .skip(1)
-                .next()
-                .ok_or(AuthError::MalformedNonce)?
-        ).map_err(|_| AuthError::MalformedNonce.into())
+        request_auth_nonce(
+            client,
+            self.file.download_url(false),
+        ).map_err(|err| PrepareError::Auth(err))
     }
 
     /// Send the request for changing the file password.
@@ -165,12 +131,6 @@ impl From<PrepareError> for Error {
     }
 }
 
-impl From<AuthError> for Error {
-    fn from(err: AuthError) -> Error {
-        PrepareError::Auth(err).into()
-    }
-}
-
 impl From<ChangeError> for Error {
     fn from(err: ChangeError) -> Error {
         Error::Change(err)
@@ -181,7 +141,7 @@ impl From<ChangeError> for Error {
 pub enum PrepareError {
     /// Failed authenticating, needed to set a new password.
     #[fail(display = "Failed to authenticate")]
-    Auth(#[cause] AuthError),
+    Auth(#[cause] NonceError),
 
     /// Some error occurred while building the data that will be sent.
     /// The owner token might possibly be missing, the wrapped error will
@@ -196,31 +156,6 @@ impl From<DataError> for PrepareError {
     }
 }
 
-#[derive(Fail, Debug)]
-pub enum AuthError {
-    /// Sending the request to gather the authentication encryption nonce
-    /// failed.
-    #[fail(display = "Failed to request authentication nonce")]
-    NonceReq,
-
-    /// The response for fetching the authentication encryption nonce
-    /// indicated an error and wasn't successful.
-    #[fail(display = "Bad HTTP response '{}' while requesting authentication nonce", _1)]
-    NonceReqStatus(StatusCode, String),
-
-    /// No authentication encryption nonce was included in the response
-    /// from the server, it was missing.
-    #[fail(display = "Missing authentication nonce in server response")]
-    NoNonceHeader,
-
-    /// The authentication encryption nonce from the response malformed or
-    /// empty.
-    /// Maybe the server responded with a new format that isn't supported yet
-    /// by this client.
-    #[fail(display = "Received malformed authentication nonce")]
-    MalformedNonce,
-}
-
 #[derive(Fail, Debug)]
 pub enum ChangeError {
     /// Sending the request to change the password failed.

+ 2 - 12
api/src/action/upload.rs

@@ -22,7 +22,7 @@ use url::{
     Url,
 };
 
-use crypto::b64;
+use api::nonce::{HEADER_NONCE, header_nonce};
 use crypto::key_set::KeySet;
 use ext::status_code::StatusCodeExt;
 use file::remote_file::RemoteFile;
@@ -45,9 +45,6 @@ use super::password::{
 
 type EncryptedReader = ProgressReader<BufReader<EncryptedFileReader>>;
 
-/// The name of the header that is used for the authentication nonce.
-const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
-
 /// A file upload action to a Send server.
 pub struct Upload {
     /// The Send host to upload the file to.
@@ -262,14 +259,7 @@ impl Upload {
         }
 
         // Try to get the nonce, don't error on failure
-        let nonce = response.headers()
-            .get_raw(HEADER_AUTH_NONCE)
-            .and_then(|h| h.one())
-            .and_then(|line| String::from_utf8(line.to_vec()).ok())
-            .and_then(|line| line.split_terminator(" ").skip(1).next()
-                .map(|line| line.to_owned())
-            )
-            .and_then(|nonce| b64::decode(&nonce).ok());
+        let nonce = header_nonce(HEADER_NONCE, &response).ok();
 
         // Decode the response
         let response: UploadResponse = match response.json() {

+ 1 - 0
api/src/api/mod.rs

@@ -1 +1,2 @@
 pub mod data;
+pub mod nonce;

+ 84 - 0
api/src/api/nonce.rs

@@ -0,0 +1,84 @@
+use url::Url;
+use reqwest::{Client, Response, StatusCode};
+
+use crypto::b64;
+use ext::status_code::StatusCodeExt;
+
+/// The name of the header the nonce is delivered in.
+pub const HEADER_NONCE: &'static str = "WWW-Authenticate";
+
+/// Do a new request, and fetch the nonce from the header with the given name.
+pub fn request_nonce(client: &Client, url: Url, header: &str)
+    -> Result<Vec<u8>, NonceError>
+{
+    // Make the request
+    let response = client.get(url)
+        .send()
+        .map_err(|_| NonceError::Req)?;
+
+    // Validate the status code
+    let status = response.status();
+    if !status.is_success() {
+        // TODO: should we check here whether a 404 is returned?
+        // // Handle expired files
+        // if status == FILE_EXPIRED_STATUS {
+        //     return Err(Error::Expired);
+        // } else {
+        return Err(NonceError::ReqStatus(status, status.err_text()).into());
+        // }
+    }
+
+    // Fetch the nonce
+    header_nonce(header, &response)
+}
+
+/// Do a new request, and fetch the authentication nonce from the header with
+/// the given name.
+pub fn request_auth_nonce(client: &Client, url: Url)
+    -> Result<Vec<u8>, NonceError>
+{
+    request_nonce(client, url, HEADER_NONCE)
+}
+
+/// Get a nonce from a header in the given response.
+pub fn header_nonce(header: &str, response: &Response)
+    -> Result<Vec<u8>, NonceError>
+{
+    // Get the authentication nonce
+    b64::decode(
+        response.headers()
+            .get_raw(header)
+            .ok_or(NonceError::NoNonceHeader)?
+            .one()
+            .ok_or(NonceError::MalformedNonce)
+            .and_then(|line| String::from_utf8(line.to_vec())
+                .map_err(|_| NonceError::MalformedNonce)
+            )?
+            .split_terminator(" ")
+            .skip(1)
+            .next()
+            .ok_or(NonceError::MalformedNonce)?
+    ).map_err(|_| NonceError::MalformedNonce.into())
+}
+
+#[derive(Fail, Debug)]
+pub enum NonceError {
+    /// Sending the request to fetch a nonce failed.
+    #[fail(display = "Failed to request nonce")]
+    Req,
+
+    /// The response for fetching the nonce indicated an error and wasn't
+    /// successful.
+    #[fail(display = "Bad HTTP response '{}' while requesting nonce", _1)]
+    ReqStatus(StatusCode, String),
+
+    /// The nonce header was missing from the request.
+    #[fail(display = "Missing nonce in server response")]
+    NoNonceHeader,
+
+    /// The received nonce could not be parsed, because it was malformed.
+    /// Maybe the server responded with a new format that isn't supported yet
+    /// by this client.
+    #[fail(display = "Received malformed nonce")]
+    MalformedNonce,
+}