mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-03 09:39:15 +02:00
Merge pull request #2 from timvisee/error-handling
Start working on improved error handling with failure crate
This commit is contained in:
commit
559c1a482f
9 changed files with 357 additions and 151 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -279,6 +279,25 @@ dependencies = [
|
||||||
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "failure"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "failure_derive"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fake-simd"
|
name = "fake-simd"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -291,6 +310,8 @@ dependencies = [
|
||||||
"arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hkdf 0.3.0 (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.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hyper 0.11.24 (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)",
|
"mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -310,6 +331,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ffsend-api 0.1.0",
|
"ffsend-api 0.1.0",
|
||||||
"open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"pbr 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"pbr 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -788,6 +810,11 @@ dependencies = [
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -1021,6 +1048,16 @@ name = "strsim"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "0.11.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.12.14"
|
version = "0.12.14"
|
||||||
|
@ -1031,6 +1068,23 @@ dependencies = [
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synom"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synstructure"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "take"
|
name = "take"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1265,6 +1319,11 @@ name = "unicode-width"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1414,6 +1473,8 @@ dependencies = [
|
||||||
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
|
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
|
||||||
"checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d"
|
"checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d"
|
||||||
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
|
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
|
||||||
|
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
||||||
|
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
||||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||||
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
@ -1471,6 +1532,7 @@ dependencies = [
|
||||||
"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
|
"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
|
||||||
"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
|
"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
|
||||||
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
|
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
|
||||||
|
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||||
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
|
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
|
||||||
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
|
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
|
||||||
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
||||||
|
@ -1499,7 +1561,10 @@ dependencies = [
|
||||||
"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d"
|
"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d"
|
||||||
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
|
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
|
||||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||||
|
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||||
"checksum syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8c5bc2d6ff27891209efa5f63e9de78648d7801f085e4653701a692ce938d6fd"
|
"checksum syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8c5bc2d6ff27891209efa5f63e9de78648d7801f085e4653701a692ce938d6fd"
|
||||||
|
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||||
|
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
||||||
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
|
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
|
||||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||||
|
@ -1524,6 +1589,7 @@ dependencies = [
|
||||||
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||||
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
|
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
|
||||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
||||||
|
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||||
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
|
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
|
||||||
|
|
|
@ -8,6 +8,8 @@ workspace = ".."
|
||||||
arrayref = "0.3"
|
arrayref = "0.3"
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
failure = "0.1"
|
||||||
|
failure_derive = "0.1"
|
||||||
hkdf = "0.3"
|
hkdf = "0.3"
|
||||||
hyper = "0.11.9" # same as reqwest
|
hyper = "0.11.9" # same as reqwest
|
||||||
mime_guess = "2.0.0-alpha.2"
|
mime_guess = "2.0.0-alpha.2"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// TODO: define redirect policy
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{
|
use std::io::{
|
||||||
self,
|
self,
|
||||||
|
@ -6,12 +8,9 @@ use std::io::{
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use failure::Error as FailureError;
|
||||||
use openssl::symm::decrypt_aead;
|
use openssl::symm::decrypt_aead;
|
||||||
use reqwest::{
|
use reqwest::{Client, Response, StatusCode};
|
||||||
Client,
|
|
||||||
Error as ReqwestError,
|
|
||||||
Response,
|
|
||||||
};
|
|
||||||
use reqwest::header::Authorization;
|
use reqwest::header::Authorization;
|
||||||
use reqwest::header::ContentLength;
|
use reqwest::header::ContentLength;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -23,13 +22,11 @@ use file::file::DownloadFile;
|
||||||
use file::metadata::Metadata;
|
use file::metadata::Metadata;
|
||||||
use reader::{EncryptedFileWriter, ProgressReporter, ProgressWriter};
|
use reader::{EncryptedFileWriter, ProgressReporter, ProgressWriter};
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
|
||||||
type StdResult<T, E> = ::std::result::Result<T, E>;
|
|
||||||
|
|
||||||
/// The name of the header that is used for the authentication nonce.
|
/// The name of the header that is used for the authentication nonce.
|
||||||
const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
|
const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
|
||||||
|
|
||||||
// TODO: experiment with `iv` of `None` in decrypt logic
|
/// The HTTP status code that is returned for expired files.
|
||||||
|
const FILE_EXPIRED_STATUS: StatusCode = StatusCode::NotFound;
|
||||||
|
|
||||||
/// A file upload action to a Send server.
|
/// A file upload action to a Send server.
|
||||||
pub struct Download<'a> {
|
pub struct Download<'a> {
|
||||||
|
@ -50,25 +47,26 @@ impl<'a> Download<'a> {
|
||||||
self,
|
self,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
reporter: Arc<Mutex<ProgressReporter>>,
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
) -> Result<()> {
|
) -> Result<(), Error> {
|
||||||
// Create a key set for the file
|
// Create a key set for the file
|
||||||
let mut key = KeySet::from(self.file);
|
let mut key = KeySet::from(self.file);
|
||||||
|
|
||||||
// Fetch the authentication nonce
|
// Fetch the authentication nonce
|
||||||
let auth_nonce = self.fetch_auth_nonce(client)
|
let auth_nonce = self.fetch_auth_nonce(client)?;
|
||||||
.map_err(|err| DownloadError::AuthError(err))?;
|
|
||||||
|
|
||||||
// Fetch the meta nonce, set the input vector
|
// Fetch the meta nonce, set the input vector
|
||||||
let meta_nonce = self.fetch_meta_nonce(&client, &mut key, auth_nonce)
|
let meta_nonce = self.fetch_meta_nonce(&client, &mut key, auth_nonce)
|
||||||
.map_err(|err| DownloadError::MetaError(err))?;
|
.map_err(|err| Error::Request(RequestError::Meta(err)))?;
|
||||||
|
|
||||||
// Open the file we will write to
|
// Open the file we will write to
|
||||||
// TODO: this should become a temporary file first
|
// TODO: this should become a temporary file first
|
||||||
|
// TODO: use the uploaded file name as default
|
||||||
let out = File::create("downloaded.zip")
|
let out = File::create("downloaded.zip")
|
||||||
.map_err(|err| DownloadError::FileOpenError(err))?;
|
.map_err(|err| Error::File(FileError::Create(err)))?;
|
||||||
|
|
||||||
// Create the file reader for downloading
|
// Create the file reader for downloading
|
||||||
let (reader, len) = self.create_file_reader(&key, meta_nonce, &client);
|
let (reader, len) = self.create_file_reader(&key, meta_nonce, &client)
|
||||||
|
.map_err(|err| Error::Download(err))?;
|
||||||
|
|
||||||
// Create the file writer
|
// Create the file writer
|
||||||
let writer = self.create_file_writer(
|
let writer = self.create_file_writer(
|
||||||
|
@ -76,10 +74,11 @@ impl<'a> Download<'a> {
|
||||||
len,
|
len,
|
||||||
&key,
|
&key,
|
||||||
reporter.clone(),
|
reporter.clone(),
|
||||||
);
|
).map_err(|err| Error::File(err))?;
|
||||||
|
|
||||||
// Download the file
|
// Download the file
|
||||||
self.download(reader, writer, len, reporter);
|
self.download(reader, writer, len, reporter)
|
||||||
|
.map_err(|err| Error::Download(err))?;
|
||||||
|
|
||||||
// TODO: return the file path
|
// TODO: return the file path
|
||||||
// TODO: return the new remote state (does it still exist remote)
|
// TODO: return the new remote state (does it still exist remote)
|
||||||
|
@ -89,35 +88,40 @@ impl<'a> Download<'a> {
|
||||||
|
|
||||||
/// Fetch the authentication nonce for the file from the Send server.
|
/// Fetch the authentication nonce for the file from the Send server.
|
||||||
fn fetch_auth_nonce(&self, client: &Client)
|
fn fetch_auth_nonce(&self, client: &Client)
|
||||||
-> StdResult<Vec<u8>, AuthError>
|
-> Result<Vec<u8>, Error>
|
||||||
{
|
{
|
||||||
// Get the download url, and parse the nonce
|
// Get the download url, and parse the nonce
|
||||||
let download_url = self.file.download_url(false);
|
let download_url = self.file.download_url(false);
|
||||||
let response = client.get(download_url)
|
let response = client.get(download_url)
|
||||||
.send()
|
.send()
|
||||||
.map_err(|_| AuthError::NonceReqFail)?;
|
.map_err(|_| AuthError::NonceReq)?;
|
||||||
|
|
||||||
// Validate the status code
|
// Validate the status code
|
||||||
// TODO: allow redirects here?
|
let status = response.status();
|
||||||
if !response.status().is_success() {
|
if !status.is_success() {
|
||||||
return Err(AuthError::NonceReqStatusErr);
|
// 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
|
// Get the authentication nonce
|
||||||
b64::decode(
|
b64::decode(
|
||||||
response.headers()
|
response.headers()
|
||||||
.get_raw(HEADER_AUTH_NONCE)
|
.get_raw(HEADER_AUTH_NONCE)
|
||||||
.ok_or(AuthError::MissingNonceHeader)?
|
.ok_or(AuthError::NoNonceHeader)?
|
||||||
.one()
|
.one()
|
||||||
.ok_or(AuthError::EmptyNonceHeader)
|
.ok_or(AuthError::MalformedNonce)
|
||||||
.and_then(|line| String::from_utf8(line.to_vec())
|
.and_then(|line| String::from_utf8(line.to_vec())
|
||||||
.map_err(|_| AuthError::MalformedNonceHeader)
|
.map_err(|_| AuthError::MalformedNonce)
|
||||||
)?
|
)?
|
||||||
.split_terminator(" ")
|
.split_terminator(" ")
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.next()
|
.next()
|
||||||
.ok_or(AuthError::MissingNonceHeader)?
|
.ok_or(AuthError::MalformedNonce)?
|
||||||
).map_err(|_| AuthError::MalformedNonce)
|
).map_err(|_| AuthError::MalformedNonce.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch the metadata nonce.
|
/// Fetch the metadata nonce.
|
||||||
|
@ -131,7 +135,7 @@ impl<'a> Download<'a> {
|
||||||
client: &Client,
|
client: &Client,
|
||||||
key: &mut KeySet,
|
key: &mut KeySet,
|
||||||
auth_nonce: Vec<u8>,
|
auth_nonce: Vec<u8>,
|
||||||
) -> StdResult<Vec<u8>, MetaError> {
|
) -> Result<Vec<u8>, MetaError> {
|
||||||
// Fetch the metadata and the nonce
|
// Fetch the metadata and the nonce
|
||||||
let (metadata, meta_nonce) = self.fetch_metadata(client, key, auth_nonce)?;
|
let (metadata, meta_nonce) = self.fetch_metadata(client, key, auth_nonce)?;
|
||||||
|
|
||||||
|
@ -151,47 +155,47 @@ impl<'a> Download<'a> {
|
||||||
client: &Client,
|
client: &Client,
|
||||||
key: &KeySet,
|
key: &KeySet,
|
||||||
auth_nonce: Vec<u8>,
|
auth_nonce: Vec<u8>,
|
||||||
) -> StdResult<(Metadata, Vec<u8>), MetaError> {
|
) -> Result<(Metadata, Vec<u8>), MetaError> {
|
||||||
// Compute the cryptographic signature for authentication
|
// Compute the cryptographic signature for authentication
|
||||||
let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce)
|
let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce)
|
||||||
.map_err(|_| MetaError::ComputeSignatureFail)?;
|
.map_err(|_| MetaError::ComputeSignature)?;
|
||||||
|
|
||||||
// Buidl the request, fetch the encrypted metadata
|
// Build the request, fetch the encrypted metadata
|
||||||
let mut response = client.get(self.file.api_meta_url())
|
let mut response = client.get(self.file.api_meta_url())
|
||||||
.header(Authorization(
|
.header(Authorization(
|
||||||
format!("send-v1 {}", sig)
|
format!("send-v1 {}", sig)
|
||||||
))
|
))
|
||||||
.send()
|
.send()
|
||||||
.map_err(|_| MetaError::NonceReqFail)?;
|
.map_err(|_| MetaError::NonceReq)?;
|
||||||
|
|
||||||
// Validate the status code
|
// Validate the status code
|
||||||
// TODO: allow redirects here?
|
let status = response.status();
|
||||||
if !response.status().is_success() {
|
if !status.is_success() {
|
||||||
return Err(MetaError::NonceReqStatusErr);
|
return Err(MetaError::NonceReqStatus(status, status.err_text()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the metadata nonce
|
// Get the metadata nonce
|
||||||
let nonce = b64::decode(
|
let nonce = b64::decode(
|
||||||
response.headers()
|
response.headers()
|
||||||
.get_raw(HEADER_AUTH_NONCE)
|
.get_raw(HEADER_AUTH_NONCE)
|
||||||
.ok_or(MetaError::MissingNonceHeader)?
|
.ok_or(MetaError::NoNonceHeader)?
|
||||||
.one()
|
.one()
|
||||||
.ok_or(MetaError::EmptyNonceHeader)
|
.ok_or(MetaError::MalformedNonce)
|
||||||
.and_then(|line| String::from_utf8(line.to_vec())
|
.and_then(|line| String::from_utf8(line.to_vec())
|
||||||
.map_err(|_| MetaError::MalformedNonceHeader)
|
.map_err(|_| MetaError::MalformedNonce)
|
||||||
)?
|
)?
|
||||||
.split_terminator(" ")
|
.split_terminator(" ")
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.next()
|
.next()
|
||||||
.ok_or(MetaError::MissingNonceHeader)?
|
.ok_or(MetaError::MalformedNonce)?
|
||||||
).map_err(|_| MetaError::MalformedNonce)?;
|
).map_err(|_| MetaError::MalformedNonce)?;
|
||||||
|
|
||||||
// Parse the metadata response, and decrypt it
|
// Parse the metadata response, and decrypt it
|
||||||
Ok((
|
Ok((
|
||||||
response.json::<MetadataResponse>()
|
response.json::<MetadataResponse>()
|
||||||
.map_err(|_| MetaError::MalformedMetadata)?
|
.map_err(|_| MetaError::Malformed)?
|
||||||
.decrypt_metadata(&key)
|
.decrypt_metadata(&key)
|
||||||
.map_err(|_| MetaError::DecryptMetadataFail)?,
|
.map_err(|_| MetaError::Decrypt)?,
|
||||||
nonce,
|
nonce,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -206,36 +210,31 @@ impl<'a> Download<'a> {
|
||||||
key: &KeySet,
|
key: &KeySet,
|
||||||
meta_nonce: Vec<u8>,
|
meta_nonce: Vec<u8>,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) -> (Response, u64) {
|
) -> Result<(Response, u64), DownloadError> {
|
||||||
// Compute the cryptographic signature
|
// 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(), &meta_nonce)
|
let sig = signature_encoded(key.auth_key().unwrap(), &meta_nonce)
|
||||||
.expect("failed to compute file signature");
|
.map_err(|_| DownloadError::ComputeSignature)?;
|
||||||
|
|
||||||
// Build and send the download request
|
// Build and send the download request
|
||||||
// TODO: do not unwrap here, return error
|
|
||||||
let response = client.get(self.file.api_download_url())
|
let response = client.get(self.file.api_download_url())
|
||||||
.header(Authorization(
|
.header(Authorization(
|
||||||
format!("send-v1 {}", sig)
|
format!("send-v1 {}", sig)
|
||||||
))
|
))
|
||||||
.send()
|
.send()
|
||||||
.expect("failed to fetch file, failed to send request");
|
.map_err(|_| DownloadError::Request)?;
|
||||||
|
|
||||||
// Validate the status code
|
// Validate the status code
|
||||||
// TODO: allow redirects here?
|
let status = response.status();
|
||||||
if !response.status().is_success() {
|
if !status.is_success() {
|
||||||
// TODO: return error here
|
return Err(DownloadError::RequestStatus(status, status.err_text()));
|
||||||
panic!("failed to fetch file, request status is not successful");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the content length
|
// Get the content length
|
||||||
// TODO: make sure there is enough disk space
|
// TODO: make sure there is enough disk space
|
||||||
let len = response.headers().get::<ContentLength>()
|
let len = response.headers().get::<ContentLength>()
|
||||||
.expect("failed to fetch file, missing content length header")
|
.ok_or(DownloadError::NoLength)?.0;
|
||||||
.0;
|
|
||||||
|
|
||||||
(response, len)
|
Ok((response, len))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a file writer.
|
/// Create a file writer.
|
||||||
|
@ -248,7 +247,7 @@ impl<'a> Download<'a> {
|
||||||
len: u64,
|
len: u64,
|
||||||
key: &KeySet,
|
key: &KeySet,
|
||||||
reporter: Arc<Mutex<ProgressReporter>>,
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
) -> ProgressWriter<EncryptedFileWriter> {
|
) -> Result<ProgressWriter<EncryptedFileWriter>, FileError> {
|
||||||
// Build an encrypted writer
|
// Build an encrypted writer
|
||||||
let mut writer = ProgressWriter::new(
|
let mut writer = ProgressWriter::new(
|
||||||
EncryptedFileWriter::new(
|
EncryptedFileWriter::new(
|
||||||
|
@ -257,13 +256,13 @@ impl<'a> Download<'a> {
|
||||||
KeySet::cipher(),
|
KeySet::cipher(),
|
||||||
key.file_key().unwrap(),
|
key.file_key().unwrap(),
|
||||||
key.iv(),
|
key.iv(),
|
||||||
).expect("failed to create encrypted writer")
|
).map_err(|_| FileError::EncryptedWriter)?
|
||||||
).expect("failed to create encrypted writer");
|
).map_err(|_| FileError::EncryptedWriter)?;
|
||||||
|
|
||||||
// Set the reporter
|
// Set the reporter
|
||||||
writer.set_reporter(reporter.clone());
|
writer.set_reporter(reporter.clone());
|
||||||
|
|
||||||
writer
|
Ok(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download the file from the reader, and write it to the writer.
|
/// Download the file from the reader, and write it to the writer.
|
||||||
|
@ -275,78 +274,27 @@ impl<'a> Download<'a> {
|
||||||
mut writer: ProgressWriter<EncryptedFileWriter>,
|
mut writer: ProgressWriter<EncryptedFileWriter>,
|
||||||
len: u64,
|
len: u64,
|
||||||
reporter: Arc<Mutex<ProgressReporter>>,
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
) {
|
) -> Result<(), DownloadError> {
|
||||||
// Start the writer
|
// Start the writer
|
||||||
reporter.lock()
|
reporter.lock()
|
||||||
.expect("unable to start progress, failed to get lock")
|
.map_err(|_| DownloadError::Progress)?
|
||||||
.start(len);
|
.start(len);
|
||||||
|
|
||||||
// Write to the output file
|
// Write to the output file
|
||||||
io::copy(&mut reader, &mut writer)
|
io::copy(&mut reader, &mut writer).map_err(|_| DownloadError::Download)?;
|
||||||
.expect("failed to download and decrypt file");
|
|
||||||
|
|
||||||
// Finish
|
// Finish
|
||||||
reporter.lock()
|
reporter.lock()
|
||||||
.expect("unable to finish progress, failed to get lock")
|
.map_err(|_| DownloadError::Progress)?
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
// Verify the writer
|
// Verify the writer
|
||||||
// TODO: delete the file if verification failed, show a proper error
|
if writer.unwrap().verified() {
|
||||||
assert!(writer.unwrap().verified(), "downloaded and decrypted file could not be verified");
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(DownloadError::Verify)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that may occur in the upload action.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum DownloadError {
|
|
||||||
/// An authentication related error.
|
|
||||||
AuthError(AuthError),
|
|
||||||
|
|
||||||
/// An metadata related error.
|
|
||||||
MetaError(MetaError),
|
|
||||||
|
|
||||||
/// An error occurred while opening the file for writing.
|
|
||||||
FileOpenError(IoError),
|
|
||||||
|
|
||||||
/// The given file is not not an existing file.
|
|
||||||
/// Maybe it is a directory, or maybe it doesn't exist.
|
|
||||||
NotAFile,
|
|
||||||
|
|
||||||
/// An error occurred while opening or reading a file.
|
|
||||||
FileError,
|
|
||||||
|
|
||||||
/// An error occurred while encrypting the file.
|
|
||||||
EncryptionError,
|
|
||||||
|
|
||||||
/// An error occurred while while processing the request.
|
|
||||||
/// This also covers things like HTTP 404 errors.
|
|
||||||
RequestError(ReqwestError),
|
|
||||||
|
|
||||||
/// An error occurred while decoding the response data.
|
|
||||||
DecodeError,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AuthError {
|
|
||||||
NonceReqFail,
|
|
||||||
NonceReqStatusErr,
|
|
||||||
MissingNonceHeader,
|
|
||||||
EmptyNonceHeader,
|
|
||||||
MalformedNonceHeader,
|
|
||||||
MalformedNonce,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum MetaError {
|
|
||||||
ComputeSignatureFail,
|
|
||||||
NonceReqFail,
|
|
||||||
NonceReqStatusErr,
|
|
||||||
MissingNonceHeader,
|
|
||||||
EmptyNonceHeader,
|
|
||||||
MalformedNonceHeader,
|
|
||||||
MalformedNonce,
|
|
||||||
MalformedMetadata,
|
|
||||||
DecryptMetadataFail,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The metadata response from the server, when fetching the data through
|
/// The metadata response from the server, when fetching the data through
|
||||||
|
@ -366,11 +314,9 @@ impl MetadataResponse {
|
||||||
///
|
///
|
||||||
/// The decrypted data is verified using an included tag.
|
/// The decrypted data is verified using an included tag.
|
||||||
/// If verification failed, an error is returned.
|
/// If verification failed, an error is returned.
|
||||||
// TODO: do not unwrap, return a proper error
|
pub fn decrypt_metadata(&self, key_set: &KeySet) -> Result<Metadata, FailureError> {
|
||||||
pub fn decrypt_metadata(&self, key_set: &KeySet) -> Result<Metadata> {
|
|
||||||
// Decode the metadata
|
// Decode the metadata
|
||||||
let raw = b64::decode(&self.meta)
|
let raw = b64::decode(&self.meta)?;
|
||||||
.expect("failed to decode metadata from server");
|
|
||||||
|
|
||||||
// Get the encrypted metadata, and it's tag
|
// Get the encrypted metadata, and it's tag
|
||||||
let (encrypted, tag) = raw.split_at(raw.len() - 16);
|
let (encrypted, tag) = raw.split_at(raw.len() - 16);
|
||||||
|
@ -378,7 +324,6 @@ impl MetadataResponse {
|
||||||
assert_eq!(tag.len(), 16);
|
assert_eq!(tag.len(), 16);
|
||||||
|
|
||||||
// Decrypt the metadata
|
// Decrypt the metadata
|
||||||
// TODO: do not unwrap, return an error
|
|
||||||
let meta = decrypt_aead(
|
let meta = decrypt_aead(
|
||||||
KeySet::cipher(),
|
KeySet::cipher(),
|
||||||
key_set.meta_key().unwrap(),
|
key_set.meta_key().unwrap(),
|
||||||
|
@ -386,12 +331,177 @@ impl MetadataResponse {
|
||||||
&[],
|
&[],
|
||||||
encrypted,
|
encrypted,
|
||||||
&tag,
|
&tag,
|
||||||
).expect("failed to decrypt metadata, invalid tag?");
|
)?;
|
||||||
|
|
||||||
// Parse the metadata, and return
|
// Parse the metadata, and return
|
||||||
Ok(
|
Ok(serde_json::from_slice(&meta)?)
|
||||||
serde_json::from_slice(&meta)
|
}
|
||||||
.expect("failed to parse decrypted metadata as JSON")
|
}
|
||||||
)
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// A general error occurred while requesting the file data.
|
||||||
|
/// This may be because authentication failed, because decrypting the
|
||||||
|
/// file metadata didn't succeed, or due to some other reason.
|
||||||
|
#[fail(display = "failed to request file data")]
|
||||||
|
Request(#[cause] RequestError),
|
||||||
|
|
||||||
|
/// The given Send file has expired, or did never exist in the first place.
|
||||||
|
/// Therefore the file could not be downloaded.
|
||||||
|
#[fail(display = "the file has expired or did never exist")]
|
||||||
|
Expired,
|
||||||
|
|
||||||
|
/// An error occurred while downloading the file.
|
||||||
|
#[fail(display = "failed to download the file")]
|
||||||
|
Download(#[cause] DownloadError),
|
||||||
|
|
||||||
|
/// An error occurred while decrypting the downloaded file.
|
||||||
|
#[fail(display = "failed to decrypt the downloaded file")]
|
||||||
|
Decrypt,
|
||||||
|
|
||||||
|
/// An error occurred while opening or writing to the target file.
|
||||||
|
// TODO: show what file this is about
|
||||||
|
#[fail(display = "could not open the file for writing")]
|
||||||
|
File(#[cause] FileError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AuthError> for Error {
|
||||||
|
fn from(err: AuthError) -> Error {
|
||||||
|
Error::Request(RequestError::Auth(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum RequestError {
|
||||||
|
/// Failed authenticating, in order to fetch the file data.
|
||||||
|
#[fail(display = "failed to authenticate")]
|
||||||
|
Auth(#[cause] AuthError),
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// decryption.
|
||||||
|
#[fail(display = "failed to compute cryptographic signature")]
|
||||||
|
ComputeSignature,
|
||||||
|
|
||||||
|
/// Sending the request to gather the metadata encryption nonce failed.
|
||||||
|
#[fail(display = "failed to request metadata nonce")]
|
||||||
|
NonceReq,
|
||||||
|
|
||||||
|
/// The response for fetching the metadata encryption nonce indicated an
|
||||||
|
/// error and wasn't successful.
|
||||||
|
#[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,
|
||||||
|
|
||||||
|
/// The received metadata is malformed, and couldn't be decoded or
|
||||||
|
/// interpreted.
|
||||||
|
#[fail(display = "received malformed metadata")]
|
||||||
|
Malformed,
|
||||||
|
|
||||||
|
/// Failed to decrypt the received metadata.
|
||||||
|
#[fail(display = "failed to decrypt received metadata")]
|
||||||
|
Decrypt,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum DownloadError {
|
||||||
|
/// An error occurred while computing the cryptographic signature used for
|
||||||
|
/// downloading the file.
|
||||||
|
#[fail(display = "failed to compute cryptographic signature")]
|
||||||
|
ComputeSignature,
|
||||||
|
|
||||||
|
/// Sending the request to gather the metadata encryption nonce failed.
|
||||||
|
#[fail(display = "failed to request file download")]
|
||||||
|
Request,
|
||||||
|
|
||||||
|
/// The response for downloading the indicated an error and wasn't successful.
|
||||||
|
#[fail(display = "bad HTTP response '{}' while requesting file download", _1)]
|
||||||
|
RequestStatus(StatusCode, String),
|
||||||
|
|
||||||
|
/// The length of the file is missing, thus the length of the file to download
|
||||||
|
/// couldn't be determined.
|
||||||
|
#[fail(display = "couldn't determine file download length, missing property")]
|
||||||
|
NoLength,
|
||||||
|
|
||||||
|
/// Failed to start or update the downloading progress, because of this the
|
||||||
|
/// download can't continue.
|
||||||
|
#[fail(display = "failed to update download progress")]
|
||||||
|
Progress,
|
||||||
|
|
||||||
|
/// The actual download and decryption process the server.
|
||||||
|
/// This covers reading the file from the server, decrypting the file,
|
||||||
|
/// and writing it to the file system.
|
||||||
|
#[fail(display = "failed to download the file")]
|
||||||
|
Download,
|
||||||
|
|
||||||
|
/// Verifiying the downloaded file failed.
|
||||||
|
#[fail(display = "file verification failed")]
|
||||||
|
Verify,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum FileError {
|
||||||
|
/// An error occurred while creating or opening the file to write to.
|
||||||
|
#[fail(display = "failed to create or open file")]
|
||||||
|
Create(#[cause] IoError),
|
||||||
|
|
||||||
|
/// Failed to create an encrypted writer for the file, which is used to
|
||||||
|
/// decrypt the downloaded file.
|
||||||
|
#[fail(display = "failed to create file decryptor")]
|
||||||
|
EncryptedWriter,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reqwest status code extention, to easily retrieve an error message.
|
||||||
|
trait StatusCodeExt {
|
||||||
|
/// Build a basic error message based on the status code.
|
||||||
|
fn err_text(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusCodeExt for StatusCode {
|
||||||
|
fn err_text(&self) -> String {
|
||||||
|
self.canonical_reason()
|
||||||
|
.map(|text| text.to_owned())
|
||||||
|
.unwrap_or(format!("{}", self.as_u16()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate arrayref;
|
extern crate arrayref;
|
||||||
|
extern crate failure;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate failure_derive;
|
||||||
extern crate mime_guess;
|
extern crate mime_guess;
|
||||||
extern crate openssl;
|
extern crate openssl;
|
||||||
pub extern crate reqwest;
|
pub extern crate reqwest;
|
||||||
pub extern crate url;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
pub extern crate url;
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
|
|
||||||
|
pub use failure::Error;
|
||||||
|
|
|
@ -14,6 +14,7 @@ default = ["clipboard"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.31"
|
clap = "2.31"
|
||||||
clipboard = { version = "0.4", optional = true }
|
clipboard = { version = "0.4", optional = true }
|
||||||
|
failure = "0.1"
|
||||||
ffsend-api = { version = "*", path = "../api" }
|
ffsend-api = { version = "*", path = "../api" }
|
||||||
open = "1"
|
open = "1"
|
||||||
pbr = "1"
|
pbr = "1"
|
||||||
|
|
|
@ -6,6 +6,7 @@ use ffsend_api::reqwest::Client;
|
||||||
|
|
||||||
use cmd::cmd_download::CmdDownload;
|
use cmd::cmd_download::CmdDownload;
|
||||||
use progress::ProgressBar;
|
use progress::ProgressBar;
|
||||||
|
use util::quit_error;
|
||||||
|
|
||||||
/// A file download action.
|
/// A file download action.
|
||||||
pub struct Download<'a> {
|
pub struct Download<'a> {
|
||||||
|
@ -38,7 +39,9 @@ impl<'a> Download<'a> {
|
||||||
|
|
||||||
// Execute an download action
|
// Execute an download action
|
||||||
// TODO: do not unwrap, but return an error
|
// TODO: do not unwrap, but return an error
|
||||||
ApiDownload::new(&file).invoke(&client, bar).unwrap();
|
if let Err(err) = ApiDownload::new(&file).invoke(&client, bar) {
|
||||||
|
quit_error(err);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: open the file, or it's location
|
// TODO: open the file, or it's location
|
||||||
// TODO: copy the file location
|
// TODO: copy the file location
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ffsend_api::url::{ParseError, Url};
|
||||||
|
|
||||||
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
use util::quit_error;
|
use util::quit_error_msg;
|
||||||
|
|
||||||
/// The download command.
|
/// The download command.
|
||||||
pub struct CmdDownload<'a> {
|
pub struct CmdDownload<'a> {
|
||||||
|
@ -47,18 +47,18 @@ impl<'a: 'b, 'b> CmdDownload<'a> {
|
||||||
match Url::parse(url) {
|
match Url::parse(url) {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(ParseError::EmptyHost) =>
|
Err(ParseError::EmptyHost) =>
|
||||||
quit_error("emtpy host given"),
|
quit_error_msg("emtpy host given"),
|
||||||
Err(ParseError::InvalidPort) =>
|
Err(ParseError::InvalidPort) =>
|
||||||
quit_error("invalid host port"),
|
quit_error_msg("invalid host port"),
|
||||||
Err(ParseError::InvalidIpv4Address) =>
|
Err(ParseError::InvalidIpv4Address) =>
|
||||||
quit_error("invalid IPv4 address in host"),
|
quit_error_msg("invalid IPv4 address in host"),
|
||||||
Err(ParseError::InvalidIpv6Address) =>
|
Err(ParseError::InvalidIpv6Address) =>
|
||||||
quit_error("invalid IPv6 address in host"),
|
quit_error_msg("invalid IPv6 address in host"),
|
||||||
Err(ParseError::InvalidDomainCharacter) =>
|
Err(ParseError::InvalidDomainCharacter) =>
|
||||||
quit_error("host domains contains an invalid character"),
|
quit_error_msg("host domains contains an invalid character"),
|
||||||
Err(ParseError::RelativeUrlWithoutBase) =>
|
Err(ParseError::RelativeUrlWithoutBase) =>
|
||||||
quit_error("host domain doesn't contain a host"),
|
quit_error_msg("host domain doesn't contain a host"),
|
||||||
_ => quit_error("the given host is invalid"),
|
_ => quit_error_msg("the given host is invalid"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use ffsend_api::url::{ParseError, Url};
|
||||||
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
use app::SEND_DEF_HOST;
|
use app::SEND_DEF_HOST;
|
||||||
use util::quit_error;
|
use util::quit_error_msg;
|
||||||
|
|
||||||
/// The upload command.
|
/// The upload command.
|
||||||
pub struct CmdUpload<'a> {
|
pub struct CmdUpload<'a> {
|
||||||
|
@ -72,18 +72,18 @@ impl<'a: 'b, 'b> CmdUpload<'a> {
|
||||||
match Url::parse(host) {
|
match Url::parse(host) {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(ParseError::EmptyHost) =>
|
Err(ParseError::EmptyHost) =>
|
||||||
quit_error("emtpy host given"),
|
quit_error_msg("emtpy host given"),
|
||||||
Err(ParseError::InvalidPort) =>
|
Err(ParseError::InvalidPort) =>
|
||||||
quit_error("invalid host port"),
|
quit_error_msg("invalid host port"),
|
||||||
Err(ParseError::InvalidIpv4Address) =>
|
Err(ParseError::InvalidIpv4Address) =>
|
||||||
quit_error("invalid IPv4 address in host"),
|
quit_error_msg("invalid IPv4 address in host"),
|
||||||
Err(ParseError::InvalidIpv6Address) =>
|
Err(ParseError::InvalidIpv6Address) =>
|
||||||
quit_error("invalid IPv6 address in host"),
|
quit_error_msg("invalid IPv6 address in host"),
|
||||||
Err(ParseError::InvalidDomainCharacter) =>
|
Err(ParseError::InvalidDomainCharacter) =>
|
||||||
quit_error("host domains contains an invalid character"),
|
quit_error_msg("host domains contains an invalid character"),
|
||||||
Err(ParseError::RelativeUrlWithoutBase) =>
|
Err(ParseError::RelativeUrlWithoutBase) =>
|
||||||
quit_error("host domain doesn't contain a host"),
|
quit_error_msg("host domain doesn't contain a host"),
|
||||||
_ => quit_error("the given host is invalid"),
|
_ => quit_error_msg("the given host is invalid"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,40 @@
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
extern crate clipboard;
|
extern crate clipboard;
|
||||||
|
extern crate failure;
|
||||||
extern crate open;
|
extern crate open;
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
use std::error::Error;
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
use std::process::{exit, ExitStatus};
|
use std::process::{exit, ExitStatus};
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
use self::clipboard::{ClipboardContext, ClipboardProvider};
|
use self::clipboard::{ClipboardContext, ClipboardProvider};
|
||||||
|
use self::failure::{Fail};
|
||||||
use ffsend_api::url::Url;
|
use ffsend_api::url::Url;
|
||||||
|
|
||||||
/// Quit the application with an error code,
|
/// Quit the application with an error code,
|
||||||
/// and print the given error message.
|
/// and print the given error.
|
||||||
pub fn quit_error<S: AsRef<str>>(err: S) -> ! {
|
pub fn quit_error<E: Fail>(err: E) -> ! {
|
||||||
// Print the error message
|
// Print the error message
|
||||||
eprintln!("error: {}", err.as_ref());
|
eprintln!("error: {}", err);
|
||||||
|
|
||||||
|
// Quit
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quit the application with an error code,
|
||||||
|
/// and print the given error message.
|
||||||
|
pub fn quit_error_msg<S>(err: S) -> !
|
||||||
|
where
|
||||||
|
S: AsRef<str> + Display + Debug + Sync + Send + 'static
|
||||||
|
{
|
||||||
|
// TODO: forward the error the `quit_error` here
|
||||||
|
// quit_error(failure::err_msg(err));
|
||||||
|
|
||||||
|
// Print the error message
|
||||||
|
eprintln!("error: {}", err);
|
||||||
|
|
||||||
// Quit
|
// Quit
|
||||||
exit(1);
|
exit(1);
|
||||||
|
@ -35,7 +54,7 @@ pub fn open_path(path: &str) -> Result<ExitStatus, IoError> {
|
||||||
|
|
||||||
/// Set the clipboard of the user to the given `content` string.
|
/// Set the clipboard of the user to the given `content` string.
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
pub fn set_clipboard(content: String) -> Result<(), Box<Error>> {
|
pub fn set_clipboard(content: String) -> Result<(), Box<StdError>> {
|
||||||
let mut context: ClipboardContext = ClipboardProvider::new()?;
|
let mut context: ClipboardContext = ClipboardProvider::new()?;
|
||||||
context.set_contents(content)
|
context.set_contents(content)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue