Browse Source

Extract ffsend-api to separate repository, move client crate to root

timvisee 7 years ago
parent
commit
8ed530a83a
83 changed files with 132 additions and 3984 deletions
  1. 2 2
      .travis.yml
  2. 79 78
      Cargo.lock
  3. 49 5
      Cargo.toml
  4. 2 6
      README.md
  5. 0 27
      api/Cargo.toml
  6. 0 20
      api/LICENSE
  7. 0 166
      api/src/action/delete.rs
  8. 0 344
      api/src/action/download.rs
  9. 0 119
      api/src/action/exists.rs
  10. 0 226
      api/src/action/info.rs
  11. 0 319
      api/src/action/metadata.rs
  12. 0 8
      api/src/action/mod.rs
  13. 0 250
      api/src/action/params.rs
  14. 0 182
      api/src/action/password.rs
  15. 0 487
      api/src/action/upload.rs
  16. 0 50
      api/src/api/data.rs
  17. 0 4
      api/src/api/mod.rs
  18. 0 81
      api/src/api/nonce.rs
  19. 0 44
      api/src/api/request.rs
  20. 0 72
      api/src/api/url.rs
  21. 0 19
      api/src/config.rs
  22. 0 33
      api/src/crypto/b64.rs
  23. 0 81
      api/src/crypto/hdkf.rs
  24. 0 153
      api/src/crypto/key_set.rs
  25. 0 7
      api/src/crypto/mod.rs
  26. 0 34
      api/src/crypto/sig.rs
  27. 0 1
      api/src/ext/mod.rs
  28. 0 15
      api/src/ext/status_code.rs
  29. 0 117
      api/src/file/metadata.rs
  30. 0 2
      api/src/file/mod.rs
  31. 0 339
      api/src/file/remote_file.rs
  32. 0 27
      api/src/lib.rs
  33. 0 617
      api/src/reader.rs
  34. 0 49
      cli/Cargo.toml
  35. 0 0
      src/action/debug.rs
  36. 0 0
      src/action/delete.rs
  37. 0 0
      src/action/download.rs
  38. 0 0
      src/action/exists.rs
  39. 0 0
      src/action/history.rs
  40. 0 0
      src/action/info.rs
  41. 0 0
      src/action/mod.rs
  42. 0 0
      src/action/params.rs
  43. 0 0
      src/action/password.rs
  44. 0 0
      src/action/upload.rs
  45. 0 0
      src/archive/archive.rs
  46. 0 0
      src/archive/archiver.rs
  47. 0 0
      src/archive/mod.rs
  48. 0 0
      src/cmd/arg/download_limit.rs
  49. 0 0
      src/cmd/arg/host.rs
  50. 0 0
      src/cmd/arg/mod.rs
  51. 0 0
      src/cmd/arg/owner.rs
  52. 0 0
      src/cmd/arg/password.rs
  53. 0 0
      src/cmd/arg/url.rs
  54. 0 0
      src/cmd/handler.rs
  55. 0 0
      src/cmd/matcher/debug.rs
  56. 0 0
      src/cmd/matcher/delete.rs
  57. 0 0
      src/cmd/matcher/download.rs
  58. 0 0
      src/cmd/matcher/exists.rs
  59. 0 0
      src/cmd/matcher/history.rs
  60. 0 0
      src/cmd/matcher/info.rs
  61. 0 0
      src/cmd/matcher/main.rs
  62. 0 0
      src/cmd/matcher/mod.rs
  63. 0 0
      src/cmd/matcher/params.rs
  64. 0 0
      src/cmd/matcher/password.rs
  65. 0 0
      src/cmd/matcher/upload.rs
  66. 0 0
      src/cmd/mod.rs
  67. 0 0
      src/cmd/subcmd/debug.rs
  68. 0 0
      src/cmd/subcmd/delete.rs
  69. 0 0
      src/cmd/subcmd/download.rs
  70. 0 0
      src/cmd/subcmd/exists.rs
  71. 0 0
      src/cmd/subcmd/history.rs
  72. 0 0
      src/cmd/subcmd/info.rs
  73. 0 0
      src/cmd/subcmd/mod.rs
  74. 0 0
      src/cmd/subcmd/params.rs
  75. 0 0
      src/cmd/subcmd/password.rs
  76. 0 0
      src/cmd/subcmd/upload.rs
  77. 0 0
      src/error.rs
  78. 0 0
      src/history.rs
  79. 0 0
      src/history_tool.rs
  80. 0 0
      src/host.rs
  81. 0 0
      src/main.rs
  82. 0 0
      src/progress.rs
  83. 0 0
      src/util.rs

+ 2 - 2
.travis.yml

@@ -20,8 +20,8 @@ script:
 - cargo build --verbose --all
 
   # Other feature combinations 
-- cargo build --package ffsend --no-default-features --verbose --all
-- cargo build --package ffsend --features no-color --verbose --all
+- cargo build --no-default-features --verbose --all
+- cargo build --features no-color --verbose --all
 
   # Tests
 - cargo test --verbose --all

+ 79 - 78
Cargo.lock

@@ -37,7 +37,7 @@ name = "atty"
 version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -49,7 +49,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -60,7 +60,7 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -137,7 +137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -151,7 +151,7 @@ dependencies = [
  "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -194,7 +194,7 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -202,7 +202,7 @@ name = "core-foundation-sys"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -365,8 +365,8 @@ dependencies = [
  "pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "prettytable-rs 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
  "tar 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "tempfile 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -386,11 +386,11 @@ dependencies = [
  "hkdf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.11.27 (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)",
- "openssl 0.10.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -404,8 +404,8 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -426,7 +426,7 @@ name = "fs2"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -545,7 +545,7 @@ name = "iovec"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -585,7 +585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "libc"
-version = "0.2.40"
+version = "0.2.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -619,7 +619,7 @@ name = "malloc_buf"
 version = "0.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -632,7 +632,7 @@ name = "memchr"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -640,7 +640,7 @@ name = "memchr"
 version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -677,7 +677,7 @@ dependencies = [
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -702,7 +702,7 @@ version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -716,7 +716,7 @@ version = "0.2.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -743,7 +743,7 @@ name = "num_cpus"
 version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -785,29 +785,30 @@ dependencies = [
  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.9.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "openssl"
-version = "0.10.7"
+version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.9.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.30"
+version = "0.9.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -818,7 +819,7 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -879,12 +880,12 @@ dependencies = [
  "encode_unicode 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "proc-macro2"
-version = "0.3.8"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -897,10 +898,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "quote"
-version = "0.5.2"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -909,7 +910,7 @@ version = "0.3.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -919,13 +920,13 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "redox_syscall"
-version = "0.1.37"
+version = "0.1.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -933,7 +934,7 @@ name = "redox_termios"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -986,7 +987,7 @@ dependencies = [
  "log 0.4.1 (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)",
  "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_urlencoded 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1002,7 +1003,7 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1047,7 +1048,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1057,22 +1058,22 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.55"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde_derive"
-version = "1.0.55"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.13.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1082,7 +1083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1092,7 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1144,11 +1145,11 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "0.13.10"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1180,8 +1181,8 @@ version = "0.4.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
  "xattr 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1199,9 +1200,9 @@ name = "tempfile"
 version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
  "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -1220,8 +1221,8 @@ name = "termion"
 version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1230,7 +1231,7 @@ name = "textwrap"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1247,8 +1248,8 @@ name = "time"
 version = "0.1.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1417,7 +1418,7 @@ name = "toml"
 version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1466,7 +1467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1502,7 +1503,7 @@ name = "url_serde"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1606,7 +1607,7 @@ name = "xattr"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1614,7 +1615,7 @@ name = "xcb"
 version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1685,7 +1686,7 @@ dependencies = [
 "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
 "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
 "checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef"
-"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
+"checksum libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)" = "ac8ebf8343a981e2fa97042b14768f02ed3e1d602eac06cae6166df3c8ced206"
 "checksum libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "1a429b86418868c7ea91ee50e9170683f47fd9d94f5375438ec86ec3adb74e8e"
 "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
 "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
@@ -1708,9 +1709,9 @@ dependencies = [
 "checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
 "checksum objc_id 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4730aa1c64d722db45f7ccc4113a3e2c465d018de6db4d3e7dfe031e8c8a297"
 "checksum open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c281318d992e4432cfa799969467003d05921582a7489a8325e37f8a450d5113"
-"checksum openssl 0.10.7 (registry+https://github.com/rust-lang/crates.io-index)" = "63c6ff2c7d9903daf9f3429eb2f6beedb15b1f7362e3529e5bf00b6caf182400"
+"checksum openssl 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)" = "736898acffb0e00a14d86c5b836aee2ca1c502efcf1c1b0d17a936dfc49ec47f"
 "checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985"
-"checksum openssl-sys 0.9.30 (registry+https://github.com/rust-lang/crates.io-index)" = "73ae718c3562989cd3a0a5c26610feca02f8116822f6f195e6cf4887481e57f5"
+"checksum openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6a27d108b29befe1822d40e2e22f85518dac59acbf7f30fdc532f48fd0a77"
 "checksum pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907"
 "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
 "checksum phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "7d37a244c75a9748e049225155f56dbcb98fe71b192fd25fd23cb914b5ad62f2"
@@ -1719,12 +1720,12 @@ dependencies = [
 "checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930"
 "checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f"
 "checksum prettytable-rs 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "34dc1f4f6dddab3bf008ecfd4fd2a631b585fbf0af123f34c1324f51a034ff5f"
-"checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7"
+"checksum proc-macro2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a45f2f0ae0b5757f6fe9e68745ba25f5246aea3598984ed81d013865873c1f84"
 "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
-"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
+"checksum quote 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e53eeda07ddbd8b057dde66d9beded11d0dfda13f0db0769e6b71d6bcf2074e"
 "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 redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
+"checksum redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "0a12d51a5b5fd700e6c757f15877685bfa04fd7eb60c108f01d045cafa0073c2"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
 "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
 "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
@@ -1740,8 +1741,8 @@ dependencies = [
 "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
 "checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332"
 "checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead"
-"checksum serde 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)" = "97f6a6c3caba0cf8f883b53331791036404ce3c1bd895961cf8bb2f8cecfd84b"
-"checksum serde_derive 1.0.55 (registry+https://github.com/rust-lang/crates.io-index)" = "f51b0ef935cf8a41a77bce553da1f8751a739b7ad82dd73669475a22e6ecedb0"
+"checksum serde 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)" = "2a4d976362a13caad61c38cf841401d2d4d480496a9391c3842c288b01f9de95"
+"checksum serde_derive 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)" = "94bb618afe46430c6b089e9b111dc5b2fcd3e26a268da0993f6d16bea51c6021"
 "checksum serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f3ad6d546e765177cf3dded3c2e424a8040f870083a0e64064746b958ece9cb1"
 "checksum serde_urlencoded 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e703cef904312097cfceab9ce131ff6bbe09e8c964a0703345a5f49238757bc1"
 "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0"
@@ -1751,7 +1752,7 @@ dependencies = [
 "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 syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
-"checksum syn 0.13.10 (registry+https://github.com/rust-lang/crates.io-index)" = "77961dcdac942fa8bc033c16f3a790b311c8a27d00811b878ebd8cf9b7ba39d5"
+"checksum syn 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "99d991a9e7c33123925e511baab68f7ec25c3795962fe326a2395e5a42a614f0"
 "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"
@@ -1784,7 +1785,7 @@ dependencies = [
 "checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a"
 "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
 "checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25"
-"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
+"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
 "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 unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"

+ 49 - 5
Cargo.toml

@@ -1,5 +1,49 @@
-[workspace]
-members = [
-    "api",
-    "cli",
-]
+[package]
+name = "ffsend"
+description = """\
+    Easily and securely share files from the command line.\n\
+    A fully featured Firefox Send client.\
+"""
+version = "0.0.1"
+authors = ["Tim Visee <https://timvisee.com/>"]
+
+[[bin]]
+path = "src/main.rs"
+name = "ffsend"
+
+[features]
+default = ["archive", "clipboard", "history"]
+
+# Compile with file archiving support
+archive = ["tar"]
+
+# Compile with file history support
+history = []
+
+# Compile without colored output support
+no-color = ["colored/no-color"]
+
+[dependencies]
+chrono = "0.4"
+clap = "2.31"
+colored = "1.6"
+derive_builder = "0.5"
+directories = "0.10"
+failure = "0.1"
+# ffsend-api = { version = "*", path = "../ffsend-api" }
+ffsend-api = "0.0"
+fs2 = "0.4"
+lazy_static = "1.0"
+open = "1"
+pbr = "1"
+prettytable-rs = "0.6"
+rpassword = "2.0"
+serde = "1.0"
+serde_derive = "1.0"
+tar = { version = "0.4", optional = true }
+tempfile = "3"
+toml = "0.4"
+version-compare = "0.0.6"
+
+[target.'cfg(not(target_os = "linux"))'.dependencies]
+clipboard = { version = "0.4", optional = true }

+ 2 - 6
README.md

@@ -173,7 +173,7 @@ Then, walk through one of the following steps to compile and install `ffsend`:
 	```bash
 	# Clone the project
 	git clone https://github.com/timvisee/ffsend.git
-	cd ffsend/cli
+	cd ffsend
 
 	# Compile and install
 	cargo install -f
@@ -342,13 +342,9 @@ This application is not affiliated with Mozilla, Firefox or Firefox Send.
 ```
 
 ## License
-The tool `ffsend` itself is released under the GNU GPL-3.0 license.
+This project is released under the GNU GPL-3.0 license.
 Check out the [LICENSE](LICENSE) file for more information. 
 
-The `ffsend-api` library that is part of this repository located [here](api),
-is intended for use in other projects and is is released under the MIT license.
-Check out the [LICENSE](api/LICENSE) file for more information.
-
 [usage-demo-asciinema]: https://asciinema.org/a/182225
 [usage-demo-gif]: ./res/ffsend-demo.gif
 [usage-demo-mp4]: ./res/ffsend-demo.mp4?raw=true

+ 0 - 27
api/Cargo.toml

@@ -1,27 +0,0 @@
-[package]
-name = "ffsend-api"
-description = "A fully featured Firefox Send API client."
-version = "0.0.1"
-authors = ["Tim Visee <https://timvisee.com/>"]
-workspace = ".."
-
-[dependencies]
-arrayref = "0.3"
-base64 = "0.9"
-chrono = {version = "0.4", features = ["serde"]}
-derive_builder = "0.5"
-failure = "0.1"
-failure_derive = "0.1"
-hkdf = "0.3"
-hyper = "0.11.9" # same as reqwest
-mime_guess = "2.0.0-alpha.2"
-openssl = "0.10"
-regex = "0.2"
-reqwest = "0.8"
-serde = "1.0"
-serde_derive = "1.0"
-serde_json = "1.0"
-sha2 = "0.7"
-time = "0.1"
-url = "1.7"
-url_serde = "0.2"

+ 0 - 20
api/LICENSE

@@ -1,20 +0,0 @@
-Copyright (c) 2017 Tim Visée
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-

+ 0 - 166
api/src/action/delete.rs

@@ -1,166 +0,0 @@
-use reqwest::Client;
-
-use api::data::{
-    Error as DataError,
-    OwnedData,
-};
-use api::nonce::{NonceError, request_nonce};
-use api::request::{ensure_success, ResponseError};
-use api::url::UrlBuilder;
-use file::remote_file::RemoteFile;
-
-/// An action to delete a remote file.
-pub struct Delete<'a> {
-    /// The remote file to delete.
-    file: &'a RemoteFile,
-
-    /// The authentication nonce.
-    /// May be an empty vector if the nonce is unknown.
-    nonce: Vec<u8>,
-}
-
-impl<'a> Delete<'a> {
-    /// Construct a new delete action for the given file.
-    pub fn new(file: &'a RemoteFile, nonce: Option<Vec<u8>>) -> Self {
-        Self {
-            file,
-            nonce: nonce.unwrap_or_default(),
-        }
-    }
-
-    /// Invoke the delete action.
-    pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
-        // Fetch the authentication nonce if not set yet
-        if self.nonce.is_empty() {
-            self.nonce = self.fetch_auth_nonce(client)?;
-        }
-
-        // Create owned data, to send to the server for authentication
-        let data = OwnedData::from(DeleteData::new(), &self.file)
-            .map_err(|err| PrepareError::DeleteData(
-                DeleteDataError::Owned(err),
-            ))?;
-
-        // Send the delete request
-        self.request_delete(client, &data)
-    }
-
-    /// Fetch the authentication nonce for the file from the remote server.
-    fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, Error>
-    {
-        request_nonce(
-            client,
-            UrlBuilder::download(self.file, false),
-        ).map_err(|err| err.into())
-    }
-
-    /// Send a request to delete the remote file, with the given data.
-    fn request_delete(
-        &self,
-        client: &Client,
-        data: &OwnedData<DeleteData>,
-    ) -> Result<(), Error> {
-        // Get the delete URL, and send the request
-        let url = UrlBuilder::api_delete(self.file);
-        let response = client.post(url)
-            .json(&data)
-            .send()
-            .map_err(|_| DeleteError::Request)?;
-
-        // Ensure the status code is succesful
-        ensure_success(&response)
-            .map_err(|err| err.into())
-    }
-}
-
-/// The delete data object.
-/// This object is currently empty, as no additional data is sent to the
-/// server.
-#[derive(Debug, Serialize, Default)]
-pub struct DeleteData { }
-
-impl DeleteData {
-    /// Constructor.
-    pub fn new() -> Self {
-        DeleteData::default()
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// An error occurred while preparing the action.
-    #[fail(display = "failed to prepare the action")]
-    Prepare(#[cause] PrepareError),
-
-    /// 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 has occurred while sending the filedeletion request.
-    #[fail(display = "failed to send the file deletion request")]
-    Delete(#[cause] DeleteError),
-}
-
-impl From<NonceError> for Error {
-    fn from(err: NonceError) -> Error {
-        match err {
-            NonceError::Expired => Error::Expired,
-            err => Error::Prepare(PrepareError::Auth(err)),
-        }
-    }
-}
-
-impl From<PrepareError> for Error {
-    fn from(err: PrepareError) -> Error {
-        Error::Prepare(err)
-    }
-}
-
-impl From<DeleteError> for Error {
-    fn from(err: DeleteError) -> Error {
-        Error::Delete(err)
-    }
-}
-
-#[derive(Debug, Fail)]
-pub enum DeleteDataError {
-    /// Some error occurred while trying to wrap the deletion data in an
-    /// owned object, which is required for authentication on the server.
-    /// The wrapped error further described the problem.
-    #[fail(display = "")]
-    Owned(#[cause] DataError),
-}
-
-#[derive(Fail, Debug)]
-pub enum PrepareError {
-    /// Failed to authenticate
-    #[fail(display = "failed to authenticate")]
-    Auth(#[cause] NonceError),
-
-    /// An error occurred while building the deletion data that will be
-    /// send to the server.
-    #[fail(display = "invalid parameters")]
-    DeleteData(#[cause] DeleteDataError),
-}
-
-#[derive(Fail, Debug)]
-pub enum DeleteError {
-    /// Sending the file deletion request failed.
-    #[fail(display = "failed to send file deletion request")]
-    Request,
-
-    /// The server responded with an error while requesting file deletion.
-    #[fail(display = "bad response from server while deleting file")]
-    Response(#[cause] ResponseError),
-}
-
-impl From<ResponseError> for Error {
-    fn from(err: ResponseError) -> Self {
-        match err {
-            ResponseError::Expired => Error::Expired,
-            err => Error::Delete(DeleteError::Response(err)),
-        }
-    }
-}

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

@@ -1,344 +0,0 @@
-use std::fs::File;
-use std::io::{
-    self,
-    Error as IoError,
-    Read,
-};
-use std::path::PathBuf;
-use std::sync::{Arc, Mutex};
-
-use reqwest::{Client, Response};
-use reqwest::header::Authorization;
-use reqwest::header::ContentLength;
-
-use api::url::UrlBuilder;
-use api::request::{ensure_success, ResponseError};
-use crypto::key_set::KeySet;
-use crypto::sig::signature_encoded;
-use file::remote_file::RemoteFile;
-use reader::{EncryptedFileWriter, ProgressReporter, ProgressWriter};
-use super::metadata::{
-    Error as MetadataError,
-    Metadata as MetadataAction,
-    MetadataResponse,
-};
-
-/// A file upload action to a Send server.
-pub struct Download<'a> {
-    /// The remote file to download.
-    file: &'a RemoteFile,
-
-    /// The target file or directory, to download the file to.
-    target: PathBuf,
-
-    /// An optional password to decrypt a protected file.
-    password: Option<String>,
-
-    /// Check whether the file exists (recommended).
-    check_exists: bool,
-
-    /// The metadata response to work with,
-    /// which will skip the internal metadata request.
-    metadata_response: Option<MetadataResponse>,
-}
-
-impl<'a> Download<'a> {
-    /// Construct a new download action for the given remote file.
-    /// It is recommended to check whether the file exists,
-    /// unless that is already done.
-    pub fn new(
-        file: &'a RemoteFile,
-        target: PathBuf,
-        password: Option<String>,
-        check_exists: bool,
-        metadata_response: Option<MetadataResponse>,
-    ) -> Self {
-        Self {
-            file,
-            target,
-            password,
-            check_exists,
-            metadata_response,
-        }
-    }
-
-    /// Invoke the download action.
-    pub fn invoke(
-        mut self,
-        client: &Client,
-        reporter: &Arc<Mutex<ProgressReporter>>,
-    ) -> Result<(), Error> {
-        // Create a key set for the file
-        let mut key = KeySet::from(self.file, self.password.as_ref());
-
-        // Get the metadata, or fetch the file metadata,
-        // then update the input vector in the key set
-        let metadata: MetadataResponse = if self.metadata_response.is_some() {
-                self.metadata_response.take().unwrap()
-            } else {
-                MetadataAction::new(
-                        self.file,
-                        self.password.clone(),
-                        self.check_exists,
-                    )
-                    .invoke(&client)?
-            };
-        key.set_iv(metadata.metadata().iv());
-
-        // Decide what actual file target to use
-        let path = self.decide_path(metadata.metadata().name());
-        let path_str = path.to_str().unwrap_or("?").to_owned();
-
-        // Open the file we will write to
-        // TODO: this should become a temporary file first
-        // TODO: use the uploaded file name as default
-        let out = File::create(path)
-            .map_err(|err| Error::File(
-                path_str.clone(),
-                FileError::Create(err),
-            ))?;
-
-        // Create the file reader for downloading
-        let (reader, len) = self.create_file_reader(
-            &key,
-            metadata.nonce(),
-            &client,
-        )?;
-
-        // Create the file writer
-        let writer = self.create_file_writer(
-            out,
-            len,
-            &key,
-            &reporter,
-        ).map_err(|err| Error::File(path_str.clone(), err))?;
-
-        // 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(())
-    }
-
-    /// Decide what path we will download the file to.
-    ///
-    /// A target file or directory, and a file name hint must be given.
-    /// The name hint can be derived from the retrieved metadata on this file.
-    ///
-    /// The name hint is used as file name, if a directory was given.
-    fn decide_path(&self, name_hint: &str) -> PathBuf {
-        // Return the target if it is an existing file
-        if self.target.is_file() {
-            return self.target.clone();
-        }
-
-        // Append the name hint if this is a directory
-        if self.target.is_dir() {
-            return self.target.join(name_hint);
-        }
-
-        // Return if the parent is an existing directory
-        if self.target.parent().map(|p| p.is_dir()).unwrap_or(false) {
-            return self.target.clone();
-        }
-
-        // TODO: are these todos below already implemented in CLI client?
-        // TODO: canonicalize the path when possible
-        // TODO: allow using `file.toml` as target without directory indication
-        // TODO: return a nice error here as the path may be invalid
-        // TODO: maybe prompt the user to create the directory
-        panic!("Invalid (non-existing) output path given, not yet supported");
-    }
-
-    /// 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: &[u8],
-        client: &Client,
-    ) -> Result<(Response, u64), DownloadError> {
-        // Compute the cryptographic signature
-        let sig = signature_encoded(key.auth_key().unwrap(), &meta_nonce)
-            .map_err(|_| DownloadError::ComputeSignature)?;
-
-        // Build and send the download request
-        let response = client.get(UrlBuilder::api_download(self.file))
-            .header(Authorization(
-                format!("send-v1 {}", sig)
-            ))
-            .send()
-            .map_err(|_| DownloadError::Request)?;
-
-        // Ensure the response is succesful
-        ensure_success(&response)
-            .map_err(DownloadError::Response)?;
-
-        // Get the content length
-        // TODO: make sure there is enough disk space
-        let len = response.headers().get::<ContentLength>()
-            .ok_or(DownloadError::NoLength)?.0;
-
-        Ok((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>>,
-    ) -> Result<ProgressWriter<EncryptedFileWriter>, FileError> {
-        // Build an encrypted writer
-        let mut writer = ProgressWriter::new(
-            EncryptedFileWriter::new(
-                file,
-                len as usize,
-                KeySet::cipher(),
-                key.file_key().unwrap(),
-                key.iv(),
-            ).map_err(|_| FileError::EncryptedWriter)?
-        ).map_err(|_| FileError::EncryptedWriter)?;
-
-        // Set the reporter
-        writer.set_reporter(reporter.clone());
-
-        Ok(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>>,
-    ) -> Result<(), DownloadError> {
-        // Start the writer
-        reporter.lock()
-            .map_err(|_| DownloadError::Progress)?
-            .start(len);
-
-        // Write to the output file
-        io::copy(&mut reader, &mut writer)
-            .map_err(|_| DownloadError::Download)?;
-
-        // Finish
-        reporter.lock()
-            .map_err(|_| DownloadError::Progress)?
-            .finish();
-
-        // Verify the writer
-        if writer.unwrap().verified() {
-            Ok(())
-        } else {
-            Err(DownloadError::Verify)
-        }
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// An error occurred while fetching the metadata of the file.
-    /// This step is required in order to succsessfully decrypt the
-    /// file that will be downloaded.
-    #[fail(display = "failed to fetch file metadata")]
-    Meta(#[cause] MetadataError),
-
-    /// 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,
-
-    /// A password is required, but was not given.
-    #[fail(display = "missing password, password required")]
-    PasswordRequired,
-
-    /// 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 = "couldn't use the target file at '{}'", _0)]
-    File(String, #[cause] FileError),
-}
-
-impl From<MetadataError> for Error {
-    fn from(err: MetadataError) -> Error {
-        match err {
-            MetadataError::Expired => Error::Expired,
-            MetadataError::PasswordRequired => Error::PasswordRequired,
-            err => Error::Meta(err),
-        }
-    }
-}
-
-impl From<DownloadError> for Error {
-    fn from(err: DownloadError) -> Error {
-        Error::Download(err)
-    }
-}
-
-#[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 download the file failed.
-    #[fail(display = "failed to request file download")]
-    Request,
-
-    /// The server responded with an error while requesting the file download.
-    #[fail(display = "bad response from server while requesting download")]
-    Response(#[cause] ResponseError),
-
-    /// 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,
-
-    /// Verifying 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 replace the 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,
-}

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

@@ -1,119 +0,0 @@
-use reqwest::Client;
-
-use api::request::{ensure_success, ResponseError};
-use api::url::UrlBuilder;
-use file::remote_file::RemoteFile;
-
-/// An action to check whether a remote file exists.
-/// This aciton returns an `ExistsResponse`, that defines whether the file
-/// exists, and whether it is protected by a password.
-pub struct Exists<'a> {
-    /// The remote file to check.
-    file: &'a RemoteFile,
-}
-
-impl<'a> Exists<'a> {
-    /// Construct a new exists action.
-    pub fn new(file: &'a RemoteFile) -> Self {
-        Self {
-            file,
-        }
-    }
-
-    /// Invoke the exists action.
-    pub fn invoke(self, client: &Client) -> Result<ExistsResponse, Error> {
-        self.check_exists(&client)
-    }
-
-    /// Send a request to check whether the file exists
-    fn check_exists(&self, client: &Client) -> Result<ExistsResponse, Error> {
-        // Get the download url, and parse the nonce
-        let exists_url = UrlBuilder::api_exists(self.file);
-        let mut response = client.get(exists_url)
-            .send()
-            .map_err(|_| Error::Request)?;
-
-        // Ensure the status code is succesful, check the expiry state
-        match ensure_success(&response) {
-            Ok(_) => {},
-            Err(ResponseError::Expired) => return Ok(
-                ExistsResponse::new(false, false)
-            ),
-            Err(err) => return Err(Error::Response(err)),
-        }
-
-        // Parse the response
-        let mut response = response.json::<ExistsResponse>()
-            .map_err(|_| Error::Malformed)?;
-        response.set_exists(true);
-
-        // TODO: fetch the metadata nonce from the response headers
-
-        Ok(response)
-    }
-}
-
-/// The exists response.
-#[derive(Debug, Deserialize)]
-pub struct ExistsResponse {
-    /// Whether the file exists.
-    #[serde(skip)]
-    exists: bool,
-
-    /// Whether this file requires a password.
-    #[serde(rename = "password")]
-    has_password: bool,
-}
-
-impl ExistsResponse {
-    /// Construct a new response.
-    pub fn new(exists: bool, has_password: bool) -> Self {
-        ExistsResponse {
-            exists,
-            has_password,
-        }
-    }
-
-    /// Whether the remote file exists on the server.
-    pub fn exists(&self) -> bool {
-        self.exists
-    }
-
-    /// Set whether the remote file exists.
-    pub fn set_exists(&mut self, exists: bool) {
-        self.exists = exists;
-    }
-
-    /// Whether the remote file is protected by a password.
-    pub fn has_password(&self) -> bool {
-        self.has_password
-    }
-}
-
-impl Default for ExistsResponse {
-    fn default() -> Self {
-        ExistsResponse {
-            exists: false,
-            has_password: false,
-        }
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// Sending the request to check whether the file exists failed.
-    #[fail(display = "failed to send request whether the file exists")]
-    Request,
-
-    /// The server responded with an error while checking whether the file
-    /// exists.
-    #[fail(display = "bad response from server while checking file existence")]
-    Response(#[cause] ResponseError),
-
-    /// The response from the server when checking if the file exists was
-    /// malformed.
-    /// Maybe the server responded with a new format that isn't supported yet
-    /// by this client.
-    #[fail(display = "received malformed authentication nonce")]
-    Malformed,
-}

+ 0 - 226
api/src/action/info.rs

@@ -1,226 +0,0 @@
-use std::cmp::max;
-
-use reqwest::{
-    Client,
-    Error as ReqwestError,
-};
-
-use api::data::{
-    Error as DataError,
-    OwnedData,
-};
-use api::nonce::{NonceError, request_nonce};
-use api::request::{ensure_success, ResponseError};
-use api::url::UrlBuilder;
-use file::remote_file::RemoteFile;
-
-/// An action to fetch info of a shared file.
-pub struct Info<'a> {
-    /// The remote file to fetch the info for.
-    file: &'a RemoteFile,
-
-    /// The authentication nonce.
-    /// May be an empty vector if the nonce is unknown.
-    nonce: Vec<u8>,
-}
-
-impl<'a> Info<'a> {
-    /// Construct a new info action for the given remote file.
-    pub fn new(file: &'a RemoteFile, nonce: Option<Vec<u8>>) -> Self {
-        Self {
-            file,
-            nonce: nonce.unwrap_or_default(),
-        }
-    }
-
-    /// Invoke the info action.
-    pub fn invoke(mut self, client: &Client) -> Result<InfoResponse, Error> {
-        // Fetch the authentication nonce if not set yet
-        if self.nonce.is_empty() {
-            self.nonce = self.fetch_auth_nonce(client)?;
-        }
-
-        // Create owned data, to send to the server for authentication
-        let data = OwnedData::from(InfoData::new(), &self.file)
-            .map_err(|err| -> PrepareError { err.into() })?;
-
-        // Send the info request
-        self.fetch_info(client, &data)
-    }
-
-    /// Fetch the authentication nonce for the file from the remote server.
-    fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, Error>
-    {
-        request_nonce(
-            client,
-            UrlBuilder::download(self.file, false),
-        ).map_err(|err| err.into())
-    }
-
-    /// Send the request for fetching the remote file info.
-    fn fetch_info(
-        &self,
-        client: &Client,
-        data: &OwnedData<InfoData>,
-    ) -> Result<InfoResponse, Error> {
-        // Get the info URL, and send the request
-        let url = UrlBuilder::api_info(self.file);
-        let mut response = client.post(url)
-            .json(&data)
-            .send()
-            .map_err(|_| InfoError::Request)?;
-
-        // Ensure the response is successful
-        ensure_success(&response)?;
-
-        // Decode the JSON response
-        let response: InfoResponse = match response.json() {
-            Ok(response) => response,
-            Err(err) => return Err(InfoError::Decode(err).into()),
-        };
-
-        Ok(response)
-    }
-}
-
-/// The info data object.
-/// This object is currently empty, as no additional data is sent to the
-/// server.
-#[derive(Debug, Serialize, Default)]
-pub struct InfoData { }
-
-impl InfoData {
-    /// Constructor.
-    pub fn new() -> Self {
-        InfoData::default()
-    }
-}
-
-/// The file info response.
-#[derive(Debug, Deserialize)]
-pub struct InfoResponse {
-    /// The download limit.
-    #[serde(rename = "dlimit")]
-    download_limit: usize,
-
-    /// The total number of times the file has been downloaded.
-    #[serde(rename = "dtotal")]
-    download_count: usize,
-
-    /// The time to live for this file in milliseconds.
-    #[serde(rename = "ttl")]
-    ttl: u64,
-}
-
-impl InfoResponse {
-    /// Get the number of times this file has been downloaded.
-    pub fn download_count(&self) -> usize {
-        self.download_count
-    }
-
-    /// Get the maximum number of times the file may be downloaded.
-    pub fn download_limit(&self) -> usize {
-        self.download_limit
-    }
-
-    /// Get the number of times this file may still be downloaded.
-    pub fn download_left(&self) -> usize {
-        max(self.download_limit() - self.download_count(), 0)
-    }
-
-    /// Get the time to live for this file, in milliseconds from the time the
-    /// request was made.
-    pub fn ttl_millis(&self) -> u64 {
-        self.ttl
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// An error occurred while preparing the action.
-    #[fail(display = "failed to prepare the action")]
-    Prepare(#[cause] PrepareError),
-
-    /// 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 has occurred while sending the info request to the server.
-    #[fail(display = "failed to send the file info request")]
-    Info(#[cause] InfoError),
-}
-
-impl From<NonceError> for Error {
-    fn from(err: NonceError) -> Error {
-        match err {
-            NonceError::Expired => Error::Expired,
-            err => Error::Prepare(PrepareError::Auth(err)),
-        }
-    }
-}
-
-impl From<PrepareError> for Error {
-    fn from(err: PrepareError) -> Error {
-        Error::Prepare(err)
-    }
-}
-
-impl From<ResponseError> for Error {
-    fn from(err: ResponseError) -> Error {
-        match err {
-            ResponseError::Expired => Error::Expired,
-            err => Error::Info(InfoError::Response(err)),
-        }
-    }
-}
-
-impl From<InfoError> for Error {
-    fn from(err: InfoError) -> Error {
-        Error::Info(err)
-    }
-}
-
-#[derive(Debug, Fail)]
-pub enum InfoDataError {
-    /// Some error occurred while trying to wrap the info data in an
-    /// owned object, which is required for authentication on the server.
-    /// The wrapped error further described the problem.
-    #[fail(display = "")]
-    Owned(#[cause] DataError),
-}
-
-#[derive(Fail, Debug)]
-pub enum PrepareError {
-    /// Failed authenticating, needed to fetch the info
-    #[fail(display = "failed to authenticate")]
-    Auth(#[cause] NonceError),
-
-    /// An error occurred while building the info data that will be
-    /// send to the server.
-    #[fail(display = "invalid parameters")]
-    InfoData(#[cause] InfoDataError),
-}
-
-impl From<DataError> for PrepareError {
-    fn from(err: DataError) -> PrepareError {
-        PrepareError::InfoData(InfoDataError::Owned(err))
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum InfoError {
-    /// Sending the request to fetch the file info failed.
-    #[fail(display = "failed to send file info request")]
-    Request,
-
-    /// The server responded with an error while fetching the file info.
-    #[fail(display = "bad response from server while fetching file info")]
-    Response(#[cause] ResponseError),
-
-    /// Failed to decode the info response from the server.
-    /// Maybe the server responded with data from a newer API version.
-    #[fail(display = "failed to decode info response")]
-    Decode(#[cause] ReqwestError),
-}

+ 0 - 319
api/src/action/metadata.rs

@@ -1,319 +0,0 @@
-use failure::Error as FailureError;
-use openssl::symm::decrypt_aead;
-use reqwest::Client;
-use reqwest::header::Authorization;
-use serde_json;
-
-use api::nonce::{header_nonce, NonceError, request_nonce};
-use api::request::{ensure_success, ResponseError};
-use api::url::UrlBuilder;
-use crypto::b64;
-use crypto::key_set::KeySet;
-use crypto::sig::signature_encoded;
-use file::metadata::Metadata as MetadataData;
-use file::remote_file::RemoteFile;
-use super::exists::{
-    Error as ExistsError,
-    Exists as ExistsAction,
-};
-
-/// An action to fetch file metadata.
-pub struct Metadata<'a> {
-    /// The remote file to fetch the metadata for.
-    file: &'a RemoteFile,
-
-    /// An optional password to decrypt a protected file.
-    password: Option<String>,
-
-    /// Check whether the file exists (recommended).
-    check_exists: bool,
-}
-
-impl<'a> Metadata<'a> {
-    /// Construct a new metadata action.
-    pub fn new(
-        file: &'a RemoteFile,
-        password: Option<String>,
-        check_exists: bool,
-    ) -> Self {
-        Self {
-            file,
-            password,
-            check_exists,
-        }
-    }
-
-    /// Invoke the metadata action.
-    pub fn invoke(self, client: &Client) -> Result<MetadataResponse, Error> {
-        // Make sure the given file exists
-        if self.check_exists {
-            let exist_response = ExistsAction::new(&self.file)
-                .invoke(&client)?;
-
-            // Return an error if the file does not exist
-            if !exist_response.exists() {
-                return Err(Error::Expired);
-            }
-
-            // Make sure a password is given when it is required
-            if self.password.is_none() && exist_response.has_password() {
-                return Err(Error::PasswordRequired);
-            }
-        }
-
-        // Create a key set for the file
-        let key = KeySet::from(self.file, self.password.as_ref());
-
-        // Fetch the authentication nonce
-        let auth_nonce = self.fetch_auth_nonce(client)?;
-
-        // Fetch the metadata and the metadata nonce, return the result
-        self.fetch_metadata(&client, &key, &auth_nonce)
-            .map_err(|err| err.into())
-    }
-
-    /// Fetch the authentication nonce for the file from the remote server.
-    fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, Error>
-    {
-        request_nonce(
-            client,
-            UrlBuilder::download(self.file, false),
-        ).map_err(|err| err.into())
-    }
-
-    /// 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: &[u8],
-    ) -> Result<MetadataResponse, MetaError> {
-        // Compute the cryptographic signature for authentication
-        let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce)
-            .map_err(|_| MetaError::ComputeSignature)?;
-
-        // Build the request, fetch the encrypted metadata
-        let mut response = client.get(UrlBuilder::api_metadata(self.file))
-            .header(Authorization(
-                format!("send-v1 {}", sig)
-            ))
-            .send()
-            .map_err(|_| MetaError::NonceRequest)?;
-
-        // Ensure the status code is successful
-        ensure_success(&response)
-            .map_err(MetaError::NonceResponse)?;
-
-        // Get the metadata nonce
-        let nonce = header_nonce(&response)
-            .map_err(MetaError::Nonce)?;
-
-        // Parse the metadata response
-        MetadataResponse::from(
-            &response.json::<RawMetadataResponse>()
-                .map_err(|_| MetaError::Malformed)?,
-            &key,
-            nonce,
-        ).map_err(|_| MetaError::Decrypt)
-    }
-}
-
-/// The metadata response from the server, when fetching the data through
-/// the API.
-/// This response contains raw metadata, which is still encrypted.
-#[derive(Debug, Deserialize)]
-pub struct RawMetadataResponse {
-    /// The encrypted metadata.
-    #[serde(rename = "metadata")]
-    meta: String,
-
-    /// The file size in bytes.
-    size: u64,
-}
-
-impl RawMetadataResponse {
-    /// Get and decrypt the metadata, based on the raw data in this response.
-    ///
-    /// The decrypted data is verified using an included tag.
-    /// If verification failed, an error is returned.
-    pub fn decrypt_metadata(&self, key_set: &KeySet) -> Result<MetadataData, FailureError> {
-        // Decode the metadata
-        let raw = b64::decode(&self.meta)?;
-
-        // Get the encrypted metadata, and it's tag
-        let (encrypted, tag) = raw.split_at(raw.len() - 16);
-        // TODO: is the tag length correct, remove assert if it is
-        assert_eq!(tag.len(), 16);
-
-        // Decrypt the metadata
-		let meta = decrypt_aead(
-			KeySet::cipher(),
-			key_set.meta_key().unwrap(),
-			Some(key_set.iv()),
-			&[],
-			encrypted,
-			&tag,
-		)?;
-
-        // Parse the metadata, and return
-        Ok(serde_json::from_slice(&meta)?)
-    }
-
-    /// Get the file size in bytes.
-    pub fn size(&self) -> u64 {
-        self.size
-    }
-}
-
-/// The decoded and decrypted metadata response, holding all the properties.
-/// This response object is returned from this action.
-pub struct MetadataResponse {
-    /// The actual metadata.
-    metadata: MetadataData,
-
-    /// The file size in bytes.
-    size: u64,
-
-    /// The metadata nonce.
-    nonce: Vec<u8>,
-}
-
-impl<'a> MetadataResponse {
-    /// Construct a new response with the given metadata and nonce.
-    pub fn new(metadata: MetadataData, size: u64, nonce: Vec<u8>) -> Self {
-        MetadataResponse {
-            metadata,
-            size,
-            nonce,
-        }
-    }
-
-    // Construct a new metadata response from the given raw metadata response,
-    // with an additional key set and nonce.
-    //
-    // This internally decrypts the metadata from the raw response.
-    // An error is returned if decrypting the metadata failed.
-    pub fn from(raw: &RawMetadataResponse, key_set: &KeySet, nonce: Vec<u8>)
-        -> Result<Self, FailureError>
-    {
-        Ok(
-            Self::new(
-                raw.decrypt_metadata(key_set)?,
-                raw.size(),
-                nonce,
-            )
-        )
-    }
-
-    /// Get the metadata.
-    pub fn metadata(&self) -> &MetadataData {
-        &self.metadata
-    }
-
-    /// Get the file size in bytes.
-    pub fn size(&self) -> u64 {
-        self.size
-    }
-
-    /// Get the nonce.
-    pub fn nonce(&self) -> &Vec<u8> {
-        &self.nonce
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// An error occurred while checking whether the file exists on the
-    /// server.
-    #[fail(display = "failed to check whether the file exists")]
-    Exists(#[cause] ExistsError),
-
-    /// 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,
-
-    /// A password is required, but was not given.
-    #[fail(display = "missing password, password required")]
-    PasswordRequired,
-}
-
-impl From<ExistsError> for Error {
-    fn from(err: ExistsError) -> Error {
-        Error::Exists(err)
-    }
-}
-
-impl From<RequestError> for Error {
-    fn from(err: RequestError) -> Error {
-        Error::Request(err)
-    }
-}
-
-impl From<MetaError> for Error {
-    fn from(err: MetaError) -> Error {
-        Error::Request(RequestError::Meta(err))
-    }
-}
-
-impl From<NonceError> for Error {
-    fn from(err: NonceError) -> Error {
-        match err {
-            NonceError::Expired => Error::Expired,
-            err => 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] NonceError),
-
-    /// Failed to retrieve the file metadata.
-    #[fail(display = "failed to retrieve file metadata")]
-    Meta(#[cause] MetaError),
-}
-
-#[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")]
-    NonceRequest,
-
-    /// The server responded with an error while fetching the metadata
-    /// encryption nonce.
-    #[fail(display = "bad response from server while fetching metadata nonce")]
-    NonceResponse(#[cause] ResponseError),
-
-    /// 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.
-    #[fail(display = "received malformed metadata")]
-    Malformed,
-
-    /// Failed to decrypt the received metadata.
-    #[fail(display = "failed to decrypt received metadata")]
-    Decrypt,
-}

+ 0 - 8
api/src/action/mod.rs

@@ -1,8 +0,0 @@
-pub mod delete;
-pub mod download;
-pub mod exists;
-pub mod info;
-pub mod metadata;
-pub mod params;
-pub mod password;
-pub mod upload;

+ 0 - 250
api/src/action/params.rs

@@ -1,250 +0,0 @@
-use reqwest::Client;
-
-use api::data::{
-    Error as DataError,
-    OwnedData,
-};
-use api::nonce::{NonceError, request_nonce};
-use api::request::{ensure_success, ResponseError};
-use api::url::UrlBuilder;
-use file::remote_file::RemoteFile;
-
-/// The default download count.
-pub const PARAMS_DEFAULT_DOWNLOAD: u8 = 1;
-pub const PARAMS_DEFAULT_DOWNLOAD_STR: &str = "1";
-
-/// The minimum allowed number of downloads, enforced by the server.
-pub const PARAMS_DOWNLOAD_MIN: u8 = 1;
-
-/// The maximum (inclusive) allowed number of downloads,
-/// enforced by the server.
-pub const PARAMS_DOWNLOAD_MAX: u8 = 20;
-
-/// An action to set parameters for a shared file.
-pub struct Params<'a> {
-    /// The remote file to change the parameters for.
-    file: &'a RemoteFile,
-
-    /// The parameter data that is sent to the server.
-    params: ParamsData,
-
-    /// The authentication nonce.
-    /// May be an empty vector if the nonce is unknown.
-    nonce: Vec<u8>,
-}
-
-impl<'a> Params<'a> {
-    /// Construct a new parameters action for the given remote file.
-    pub fn new(
-        file: &'a RemoteFile,
-        params: ParamsData,
-        nonce: Option<Vec<u8>>,
-    ) -> Self {
-        Self {
-            file,
-            params,
-            nonce: nonce.unwrap_or_default(),
-        }
-    }
-
-    /// Invoke the parameters action.
-    pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
-        // TODO: validate that the parameters object isn't empty
-
-        // Fetch the authentication nonce if not set yet
-        if self.nonce.is_empty() {
-            self.nonce = self.fetch_auth_nonce(client)?;
-        }
-
-        // Wrap the parameters data
-        let data = OwnedData::from(self.params.clone(), &self.file)
-            .map_err(|err| -> PrepareError { err.into() })?;
-
-        // Send the request to change the parameters
-        self.change_params(client, &data)
-    }
-
-    /// Fetch the authentication nonce for the file from the remote server.
-    fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, Error>
-    {
-        request_nonce(
-            client,
-            UrlBuilder::download(self.file, false),
-        ).map_err(|err| err.into())
-    }
-
-    /// Send the request for changing the parameters.
-    fn change_params(
-        &self,
-        client: &Client,
-        data: &OwnedData<ParamsData>,
-    ) -> Result<(), Error> {
-        // Get the params URL, and send the change
-        let url = UrlBuilder::api_params(self.file);
-        let response = client.post(url)
-            .json(&data)
-            .send()
-            .map_err(|_| ChangeError::Request)?;
-
-        // Ensure the response is successful
-        ensure_success(&response)
-            .map_err(|err| err.into())
-    }
-}
-
-/// The parameters data object, that is sent to the server.
-// TODO: make sure downloads are in-bound when using the builder
-#[derive(Clone, Debug, Builder, Serialize)]
-pub struct ParamsData {
-    /// The number of times this file may be downloaded.
-    /// This value must be in the `(0,20)` bounds, as enforced by Send servers.
-    #[serde(rename = "dlimit")]
-    download_limit: Option<u8>,
-}
-
-impl ParamsData {
-    /// Construct a new parameters object, that is empty.
-    pub fn new() -> Self {
-        ParamsData {
-            download_limit: None,
-        }
-    }
-
-    /// Create a new parameters data object, with the given parameters.
-    // TODO: the downloads must be between bounds
-    pub fn from(download_limit: Option<u8>) -> Self {
-        ParamsData {
-            download_limit,
-        }
-    }
-
-    /// Set the maximum number of allowed downloads, after which the file
-    /// will be removed.
-    ///
-    /// `None` may be given, to keep this parameter as is.
-    ///
-    /// An error may be returned if the download value is out of the allowed
-    /// bound. These bounds are fixed and enforced by the server.
-    /// See `PARAMS_DOWNLOAD_MIN` and `PARAMS_DOWNLOAD_MAX`.
-    pub fn set_download_limit(&mut self, download_limit: Option<u8>)
-        -> Result<(), ParamsDataError>
-    {
-        // Check the download limit bounds
-        if let Some(d) = download_limit {
-            if d < PARAMS_DOWNLOAD_MIN || d > PARAMS_DOWNLOAD_MAX {
-                return Err(ParamsDataError::DownloadBounds);
-            }
-        }
-
-        // Set the download limit
-        self.download_limit = download_limit;
-        Ok(())
-    }
-
-    /// Check whether this parameters object is empty,
-    /// and wouldn't change any parameter on the server when sent.
-    /// Sending an empty parameter data object would thus be useless.
-    pub fn is_empty(&self) -> bool {
-        self.download_limit.is_none()
-    }
-}
-
-impl Default for ParamsData {
-    fn default() -> ParamsData {
-        ParamsData {
-            download_limit: None,
-        }
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// An error occurred while preparing the action.
-    #[fail(display = "failed to prepare setting the parameters")]
-    Prepare(#[cause] PrepareError),
-
-    /// 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 has occurred while sending the parameter change request to
-    /// the server.
-    #[fail(display = "failed to send the parameter change request")]
-    Change(#[cause] ChangeError),
-}
-
-impl From<NonceError> for Error {
-    fn from(err: NonceError) -> Error {
-        match err {
-            NonceError::Expired => Error::Expired,
-            err => Error::Prepare(PrepareError::Auth(err)),
-        }
-    }
-}
-
-impl From<PrepareError> for Error {
-    fn from(err: PrepareError) -> Error {
-        Error::Prepare(err)
-    }
-}
-
-impl From<ChangeError> for Error {
-    fn from(err:ChangeError) -> Error {
-        Error::Change(err)
-    }
-}
-
-impl From<ResponseError> for Error {
-    fn from(err: ResponseError) -> Error {
-        match err {
-            ResponseError::Expired => Error::Expired,
-            err => Error::Change(ChangeError::Response(err)),
-        }
-    }
-}
-
-#[derive(Debug, Fail)]
-pub enum ParamsDataError {
-    /// The number of downloads is invalid, as it was out of the allowed
-    /// bounds. See `PARAMS_DOWNLOAD_MIN` and `PARAMS_DOWNLOAD_MAX`.
-    // TODO: use bound values from constants, don't hardcode them here
-    #[fail(display = "invalid number of downloads, must be between 1 and 20")]
-    DownloadBounds,
-
-    /// Some error occurred while trying to wrap the parameter data in an
-    /// owned object, which is required for authentication on the server.
-    /// The wrapped error further described the problem.
-    #[fail(display = "")]
-    Owned(#[cause] DataError),
-}
-
-#[derive(Fail, Debug)]
-pub enum PrepareError {
-    /// Failed authenticating, needed to change the parameters.
-    #[fail(display = "failed to authenticate")]
-    Auth(#[cause] NonceError),
-
-    /// An error occurred while building the parameter data that will be send
-    /// to the server.
-    #[fail(display = "invalid parameters")]
-    ParamsData(#[cause] ParamsDataError),
-}
-
-impl From<DataError> for PrepareError {
-    fn from(err: DataError) -> PrepareError {
-        PrepareError::ParamsData(ParamsDataError::Owned(err))
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum ChangeError {
-    /// Sending the request to change the parameters failed.
-    #[fail(display = "failed to send parameter change request")]
-    Request,
-
-    /// The server responded with an error while changing the file parameters.
-    #[fail(display = "bad response from server while changing parameters")]
-    Response(#[cause] ResponseError),
-}

+ 0 - 182
api/src/action/password.rs

@@ -1,182 +0,0 @@
-use reqwest::Client;
-
-use api::data::{
-    Error as DataError,
-    OwnedData,
-};
-use api::nonce::{NonceError, request_nonce};
-use api::request::{ensure_success, ResponseError};
-use api::url::UrlBuilder;
-use crypto::key_set::KeySet;
-use file::remote_file::RemoteFile;
-
-/// An action to change a password of an uploaded Send file.
-pub struct Password<'a> {
-    /// The remote file to change the password for.
-    file: &'a RemoteFile,
-
-    /// The new password to use for the file.
-    password: &'a str,
-
-    /// The authentication nonce.
-    /// May be an empty vector if the nonce is unknown.
-    nonce: Vec<u8>,
-}
-
-impl<'a> Password<'a> {
-    /// Construct a new password action for the given remote file.
-    pub fn new(
-        file: &'a RemoteFile,
-        password: &'a str,
-        nonce: Option<Vec<u8>>,
-    ) -> Self {
-        Self {
-            file,
-            password,
-            nonce: nonce.unwrap_or_default(),
-        }
-    }
-
-    /// Invoke the password action.
-    pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
-        // Create a key set for the file
-        let mut key = KeySet::from(self.file, None);
-
-        // Fetch the authentication nonce if not set yet
-        if self.nonce.is_empty() {
-            self.nonce = self.fetch_auth_nonce(client)?;
-        }
-
-        // Derive a new authentication key
-        key.derive_auth_password(self.password, &UrlBuilder::download(self.file, true));
-
-        // Build the password data, wrap it as owned
-        let data = OwnedData::from(PasswordData::from(&key), &self.file)
-            .map_err(|err| -> PrepareError { err.into() })?;
-
-        // Send the request to change the password
-        self.change_password(client, &data)
-    }
-
-    /// Fetch the authentication nonce for the file from the Send server.
-    fn fetch_auth_nonce(&self, client: &Client)
-        -> Result<Vec<u8>, Error>
-    {
-        request_nonce(
-            client,
-            UrlBuilder::download(self.file, false),
-        ).map_err(|err| err.into())
-    }
-
-    /// Send the request for changing the file password.
-    fn change_password(
-        &self,
-        client: &Client,
-        data: &OwnedData<PasswordData>,
-    ) -> Result<(), Error> {
-        // Get the password URL, and send the change
-        let url = UrlBuilder::api_password(self.file);
-        let response = client.post(url)
-            .json(&data)
-            .send()
-            .map_err(|_| ChangeError::Request)?;
-
-        // Ensure the response is successful
-        ensure_success(&response)
-            .map_err(|err| err.into())
-    }
-}
-
-/// The data object to send to the password endpoint,
-/// which sets the file password.
-#[derive(Debug, Serialize)]
-struct PasswordData {
-    /// The authentication key
-    auth: String,
-}
-
-impl PasswordData {
-    /// Create the password data object from the given key set.
-    pub fn from(key: &KeySet) -> PasswordData {
-        PasswordData {
-            auth: key.auth_key_encoded().unwrap(),
-        }
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// An error occurred while preparing the action.
-    #[fail(display = "failed to prepare setting the password")]
-    Prepare(#[cause] PrepareError),
-
-    /// 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 has occurred while sending the password change request to
-    /// the server.
-    #[fail(display = "failed to send the password change request")]
-    Change(#[cause] ChangeError),
-}
-
-impl From<NonceError> for Error {
-    fn from(err: NonceError) -> Error {
-        match err {
-            NonceError::Expired => Error::Expired,
-            err => Error::Prepare(PrepareError::Auth(err)),
-        }
-    }
-}
-
-impl From<PrepareError> for Error {
-    fn from(err: PrepareError) -> Error {
-        Error::Prepare(err)
-    }
-}
-
-impl From<ChangeError> for Error {
-    fn from(err: ChangeError) -> Error {
-        Error::Change(err)
-    }
-}
-
-impl From<ResponseError> for Error {
-    fn from(err: ResponseError) -> Error {
-        match err {
-            ResponseError::Expired => Error::Expired,
-            err => Error::Change(ChangeError::Response(err)),
-        }
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum PrepareError {
-    /// Failed authenticating, needed to set a new password.
-    #[fail(display = "failed to authenticate")]
-    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
-    /// describe this further.
-    #[fail(display = "")]
-    Data(#[cause] DataError),
-}
-
-impl From<DataError> for PrepareError {
-    fn from(err: DataError) -> PrepareError {
-        PrepareError::Data(err)
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum ChangeError {
-    /// Sending the request to change the password failed.
-    #[fail(display = "failed to send password change request")]
-    Request,
-
-    /// The server responded with an error while changing the file password.
-    #[fail(display = "bad response from server while changing password")]
-    Response(#[cause] ResponseError),
-}

+ 0 - 487
api/src/action/upload.rs

@@ -1,487 +0,0 @@
-use std::fs::File;
-use std::io::{
-    BufReader,
-    Error as IoError,
-};
-use std::path::PathBuf;
-use std::sync::{Arc, Mutex};
-
-use mime_guess::{guess_mime_type, Mime};
-use openssl::symm::encrypt_aead;
-use reqwest::{
-    Client, 
-    Error as ReqwestError,
-    Request,
-};
-use reqwest::header::Authorization;
-use reqwest::mime::APPLICATION_OCTET_STREAM;
-use reqwest::multipart::{Form, Part};
-use url::{
-    ParseError as UrlParseError,
-    Url,
-};
-
-use api::nonce::header_nonce;
-use api::request::{ensure_success, ResponseError};
-use crypto::key_set::KeySet;
-use file::remote_file::RemoteFile;
-use file::metadata::{Metadata, XFileMetadata};
-use reader::{
-    EncryptedFileReader,
-    ExactLengthReader,
-    ProgressReader,
-    ProgressReporter,
-};
-use super::params::{
-    Error as ParamsError,
-    Params,
-    ParamsData,
-};
-use super::password::{
-    Error as PasswordError,
-    Password,
-};
-
-type EncryptedReader = ProgressReader<BufReader<EncryptedFileReader>>;
-
-/// A file upload action to a Send server.
-pub struct Upload {
-    /// The Send host to upload the file to.
-    host: Url,
-
-    /// The file to upload.
-    path: PathBuf,
-
-    /// The name of the file being uploaded.
-    /// This has no relation to the file path, and will become the name of the
-    /// shared file if set.
-    name: Option<String>,
-
-    /// An optional password to protect the file with.
-    password: Option<String>,
-
-    /// Optional file parameters to set.
-    params: Option<ParamsData>,
-}
-
-impl Upload {
-    /// Construct a new upload action.
-    pub fn new(
-        host: Url,
-        path: PathBuf,
-        name: Option<String>,
-        password: Option<String>,
-        params: Option<ParamsData>,
-    ) -> Self {
-        Self {
-            host,
-            path,
-            name,
-            password,
-            params,
-        }
-    }
-
-    /// Invoke the upload action.
-    pub fn invoke(
-        self,
-        client: &Client,
-        reporter: &Arc<Mutex<ProgressReporter>>,
-    ) -> Result<RemoteFile, Error> {
-        // Create file data, generate a key
-        let file = FileData::from(&self.path)?;
-        let key = KeySet::generate(true);
-
-        // Create 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()
-            .map_err(|_| UploadError::Progress)?
-            .start(reader_len);
-
-        // Execute the request
-        let (result, nonce) = self.execute_request(req, client, &key)?;
-
-        // Mark the reporter as finished
-        reporter.lock()
-            .map_err(|_| UploadError::Progress)?
-            .finish();
-
-        // Change the password if set
-        if let Some(password) = self.password {
-            Password::new(&result, &password, nonce.clone()).invoke(client)?;
-        }
-
-        // Change parameters if set
-        if let Some(params) = self.params {
-            Params::new(&result, params, nonce.clone()).invoke(client)?;
-        }
-
-        Ok(result)
-    }
-
-    /// Create a blob of encrypted metadata.
-    fn create_metadata(&self, key: &KeySet, file: &FileData)
-        -> Result<Vec<u8>, MetaError>
-    {
-        // Determine what filename to use
-        let name = self.name.clone()
-            .unwrap_or_else(|| file.name().to_owned());
-
-        // Construct the metadata
-        let metadata = Metadata::from(
-            key.iv(),
-            name,
-            &file.mime(),
-        ).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(MetaError::Encrypt),
-        };
-
-        // 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, Error> {
-        // Open the file
-        let file = match File::open(self.path.as_path()) {
-            Ok(file) => file,
-            Err(err) => return Err(FileError::Open(err).into()),
-        };
-
-        // Create an encrypted reader
-        let reader = match EncryptedFileReader::new(
-            file,
-            KeySet::cipher(),
-            key.file_key().unwrap(),
-            key.iv(),
-        ) {
-            Ok(reader) => reader,
-            Err(_) => return Err(ReaderError::Encrypt.into()),
-        };
-
-        // Buffer the encrypted reader
-        let reader = BufReader::new(reader);
-
-        // Wrap into the encrypted reader
-        let mut reader = ProgressReader::new(reader)
-            .map_err(|_| ReaderError::Progress)?;
-
-        // 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: &[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)
-            .mime(APPLICATION_OCTET_STREAM);
-        let form = Form::new()
-            .part("data", part);
-
-        // Define the URL to call
-        // TODO: create an error for this unwrap
-        let url = self.host.join("api/upload")
-            .expect("invalid host");
-
-        // Build the request
-        // TODO: create an error for this unwrap
-        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<(RemoteFile, Option<Vec<u8>>), UploadError>
-    {
-        // Execute the request
-        let mut response = match client.execute(req) {
-            Ok(response) => response,
-            // TODO: attach the error context
-            Err(_) => return Err(UploadError::Request),
-        };
-
-        // Ensure the response is successful
-        ensure_success(&response)
-            .map_err(UploadError::Response)?;
-
-        // Try to get the nonce, don't error on failure
-        let nonce = header_nonce(&response).ok();
-
-        // Decode the response
-        let response: UploadResponse = match response.json() {
-            Ok(response) => response,
-            Err(err) => return Err(UploadError::Decode(err)),
-        };
-
-        // Transform the responce into a file object
-        Ok((
-            response.into_file(self.host.clone(), &key)?,
-            nonce,
-        ))
-    }
-}
-
-/// The response from the server after a file has been uploaded.
-/// This response contains the file ID and owner key, to manage the file.
-///
-/// It also contains the download URL, although an additional secret is
-/// required.
-///
-/// The download URL can be generated using `download_url()` which will
-/// include the required secret in the URL.
-#[derive(Debug, Deserialize)]
-struct UploadResponse {
-    /// The file ID.
-    id: String,
-
-    /// The URL the file is reachable at.
-    /// This includes the file ID, but does not include the secret.
-    url: String,
-
-    /// The owner key, used to do further file modifications.
-    owner: String,
-}
-
-impl UploadResponse {
-    /// Convert this response into a file object.
-    ///
-    /// The `host` and `key` must be given.
-    pub fn into_file(self, host: Url, key: &KeySet)
-        -> Result<RemoteFile, UploadError>
-    {
-        Ok(
-            RemoteFile::new_now(
-                self.id,
-                host,
-                Url::parse(&self.url)?,
-                key.secret().to_vec(),
-                Some(self.owner),
-            )
-        )
-    }
-}
-
-/// A struct that holds various file properties, such as it's name and it's
-/// mime type.
-struct FileData<'a> {
-    /// The file name.
-    name: &'a str,
-
-    /// The file mime type.
-    mime: Mime,
-}
-
-impl<'a> FileData<'a> {
-    /// Create a file data object, from the file at the given path.
-    pub fn from(path: &'a PathBuf) -> Result<Self, FileError> {
-        // Make sure the given path is a file
-        if !path.is_file() {
-            return Err(FileError::NotAFile);
-        }
-
-        // Get the file name
-        let name = match path.file_name() {
-            Some(name) => name.to_str().unwrap_or("file"),
-            None => "file",
-        };
-
-        Ok(
-            Self {
-                name,
-                mime: guess_mime_type(path),
-            }
-        )
-    }
-
-    /// Get the file name.
-    pub fn name(&self) -> &str {
-        self.name
-    }
-
-    /// Get the file mime type.
-    pub fn mime(&self) -> &Mime {
-        &self.mime
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum Error {
-    /// An error occurred while preparing a file for uploading.
-    #[fail(display = "failed to prepare uploading the file")]
-    Prepare(#[cause] PrepareError),
-
-    /// An error occurred while opening, reading or using the file that
-    /// the should be uploaded.
-    // TODO: maybe append the file path here for further information
-    #[fail(display = "")]
-    File(#[cause] FileError),
-
-    /// An error occurred while uploading the file.
-    #[fail(display = "failed to upload the file")]
-    Upload(#[cause] UploadError),
-
-    /// An error occurred while chaining file parameters.
-    #[fail(display = "failed to change file parameters")]
-    Params(#[cause] ParamsError),
-
-    /// An error occurred while setting the password.
-    #[fail(display = "failed to set the password")]
-    Password(#[cause] PasswordError),
-}
-
-impl From<MetaError> for Error {
-    fn from(err: MetaError) -> Error {
-        Error::Prepare(PrepareError::Meta(err))
-    }
-}
-
-impl From<FileError> for Error {
-    fn from(err: FileError) -> Error {
-        Error::File(err)
-    }
-}
-
-impl From<ReaderError> for Error {
-    fn from(err: ReaderError) -> Error {
-        Error::Prepare(PrepareError::Reader(err))
-    }
-}
-
-impl From<UploadError> for Error {
-    fn from(err: UploadError) -> Error {
-        Error::Upload(err)
-    }
-}
-
-impl From<ParamsError> for Error {
-    fn from(err: ParamsError) -> Error {
-        Error::Params(err)
-    }
-}
-
-impl From<PasswordError> for Error {
-    fn from(err: PasswordError) -> Error {
-        Error::Password(err)
-    }
-}
-
-#[derive(Fail, Debug)]
-pub enum PrepareError {
-    /// Failed to prepare the file metadata for uploading.
-    #[fail(display = "failed to prepare file metadata")]
-    Meta(#[cause] MetaError),
-
-    /// Failed to create an encrypted file reader, that encrypts
-    /// the file on the fly when it is read.
-    #[fail(display = "failed to access the file to upload")]
-    Reader(#[cause] ReaderError),
-}
-
-#[derive(Fail, Debug)]
-pub enum MetaError {
-    /// An error occurred while encrypting the file metadata.
-    #[fail(display = "failed to encrypt file metadata")]
-    Encrypt,
-}
-
-#[derive(Fail, Debug)]
-pub enum ReaderError {
-    /// An error occurred while creating the file encryptor.
-    #[fail(display = "failed to create file encryptor")]
-    Encrypt,
-
-    /// Failed to create the progress reader, attached to the file reader,
-    /// to measure the uploading progress.
-    #[fail(display = "failed to create progress reader")]
-    Progress,
-}
-
-#[derive(Fail, Debug)]
-pub enum FileError {
-    /// The given path, is not not a file or doesn't exist.
-    #[fail(display = "the given path is not an existing file")]
-    NotAFile,
-
-    /// Failed to open the file that must be uploaded for reading.
-    #[fail(display = "failed to open the file to upload")]
-    Open(#[cause] IoError),
-}
-
-#[derive(Fail, Debug)]
-pub enum UploadError {
-    /// Failed to start or update the uploading progress, because of this the
-    /// upload can't continue.
-    #[fail(display = "failed to update upload progress")]
-    Progress,
-
-    /// Sending the request to upload the file failed.
-    #[fail(display = "failed to request file upload")]
-    Request,
-
-    /// The server responded with an error while uploading.
-    #[fail(display = "bad response from server while uploading")]
-    Response(#[cause] ResponseError),
-
-    /// Failed to decode the upload response from the server.
-    /// Maybe the server responded with data from a newer API version.
-    #[fail(display = "failed to decode upload response")]
-    Decode(#[cause] ReqwestError),
-
-    /// Failed to parse the retrieved URL from the upload response.
-    #[fail(display = "failed to parse received URL")]
-    ParseUrl(#[cause] UrlParseError),
-}
-
-impl From<UrlParseError> for UploadError {
-    fn from(err: UrlParseError) -> UploadError {
-        UploadError::ParseUrl(err)
-    }
-}

+ 0 - 50
api/src/api/data.rs

@@ -1,50 +0,0 @@
-use serde::Serialize;
-
-use file::remote_file::RemoteFile;
-
-/// An owned data structure, that wraps generic data.
-/// This structure is used to send owned data to the Send server.
-/// This owned data is authenticated using an `owner_token`,
-/// which this structure manages.
-#[derive(Debug, Serialize)]
-pub struct OwnedData<D> {
-    /// The owner token, used for request authentication purposes.
-    owner_token: String,
-
-    /// The wrapped data structure.
-    #[serde(flatten)]
-    inner: D,
-}
-
-impl<D> OwnedData<D>
-    where
-        D: Serialize,
-{
-    /// Constructor.
-    pub fn new(owner_token: String, inner: D) -> Self {
-        OwnedData {
-            owner_token,
-            inner,
-        }
-    }
-
-    /// Wrap the given data structure with this owned data structure.
-    /// A `file` must be given, having a set owner token.
-    pub fn from(inner: D, file: &RemoteFile) -> Result<Self, Error> {
-        Ok(
-            Self::new(
-                file.owner_token()
-                    .ok_or(Error::NoOwnerToken)?
-                    .to_owned(),
-                inner,
-            )
-        )
-    }
-}
-
-#[derive(Debug, Fail)]
-pub enum Error {
-    /// Missing owner token, which is required.
-    #[fail(display = "missing owner token, must be specified")]
-    NoOwnerToken,
-}

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

@@ -1,4 +0,0 @@
-pub mod data;
-pub mod nonce;
-pub mod url;
-pub mod request;

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

@@ -1,81 +0,0 @@
-use url::Url;
-use reqwest::{Client, Response};
-
-use api::request::{ensure_success, ResponseError};
-use crypto::b64;
-
-/// The name of the header the nonce is delivered in.
-const HEADER_NONCE: &str = "WWW-Authenticate";
-
-/// Do a new request, and extract the nonce from a header in the given
-/// response.
-pub fn request_nonce(client: &Client, url: Url)
-    -> Result<Vec<u8>, NonceError>
-{
-    // Make the request
-    let response = client.get(url)
-        .send()
-        .map_err(|_| NonceError::Request)?; 
-
-    // Ensure the response is successful
-    ensure_success(&response)?;
-
-    // Extract the nonce
-    header_nonce(&response)
-}
-
-/// Extract the nonce from a header in the given response.
-pub fn header_nonce(response: &Response)
-    -> Result<Vec<u8>, NonceError>
-{
-    // Get the authentication nonce
-    b64::decode(
-        response.headers()
-            .get_raw(HEADER_NONCE)
-            .ok_or(NonceError::NoNonceHeader)?
-            .one()
-            .ok_or(NonceError::MalformedNonce)
-            .and_then(|line| String::from_utf8(line.to_vec())
-                .map_err(|_| NonceError::MalformedNonce)
-            )?
-            .split_terminator(' ')
-            .nth(1)
-            .ok_or(NonceError::MalformedNonce)?
-    ).map_err(|_| NonceError::MalformedNonce)
-}
-
-#[derive(Fail, Debug)]
-pub enum NonceError {
-    /// Sending the request to fetch a nonce failed,
-    /// as the file has expired or did never exist.
-    #[fail(display = "the file has expired or did never exist")]
-    Expired,
-
-    /// Sending the request to fetch a nonce failed.
-    #[fail(display = "failed to request encryption nonce")]
-    Request,
-
-    /// The server responded with an error while requesting the encryption nonce,
-    /// required for some operations.
-    #[fail(display = "bad response from server while requesting encryption nonce")]
-    Response(#[cause] ResponseError),
-
-    /// 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,
-}
-
-impl From<ResponseError> for NonceError {
-    fn from(err: ResponseError) -> Self {
-        match err {
-            ResponseError::Expired => NonceError::Expired,
-            err => NonceError::Response(err),
-        }
-    }
-}

+ 0 - 44
api/src/api/request.rs

@@ -1,44 +0,0 @@
-use reqwest::{Response, StatusCode};
-
-use config::{HTTP_STATUS_EXPIRED, HTTP_STATUS_UNAUTHORIZED};
-use ext::status_code::StatusCodeExt;
-
-/// Ensure the given response is successful. IF it isn
-pub fn ensure_success(response: &Response) -> Result<(), ResponseError> {
-    // Get the status
-    let status = response.status();
-
-    // Stop if succesful
-    if status.is_success() {
-        return Ok(());
-    }
-
-    // Handle the expired file error
-    if status == HTTP_STATUS_EXPIRED {
-        return Err(ResponseError::Expired);
-    }
-
-    // Handle the authentication issue error
-    if status == HTTP_STATUS_UNAUTHORIZED {
-        return Err(ResponseError::Unauthorized);
-    }
-
-    // Return the other error
-    Err(ResponseError::Other(status, status.err_text()))
-}
-
-#[derive(Fail, Debug)]
-pub enum ResponseError {
-    /// This request lead to an expired file, or a file that never existed.
-    #[fail(display = "this file has expired or did never exist")]
-    Expired,
-
-    /// We were unauthorized to make this request.
-    /// This is usually because of an incorrect password.
-    #[fail(display = "unauthorized, are the credentials correct?")]
-    Unauthorized,
-
-    /// Some undefined error occurred with this response.
-    #[fail(display = "bad HTTP response: {}", _1)]
-    Other(StatusCode, String),
-}

+ 0 - 72
api/src/api/url.rs

@@ -1,72 +0,0 @@
-use url::Url;
-
-use file::remote_file::RemoteFile;
-
-/// A struct, that helps building URLs for communicating with a remote host.
-pub struct UrlBuilder;
-
-impl UrlBuilder {
-    /// Get the download URL of the given file.
-    /// This URL is identical to the share URL, a term used in this API.
-    /// Set `secret` to `true`, to include it in the URL if known.
-    pub fn download(file: &RemoteFile, secret: bool) -> Url {
-        // Get the share URL, and update the secret fragment
-        let mut url = file.url().clone();
-        if secret && file.has_secret() {
-            url.set_fragment(Some(&file.secret()));
-        } else {
-            url.set_fragment(None);
-        }
-
-        url
-    }
-
-    /// Generate an API file URL, with the given endpoint.
-    /// The endpoint should not contain any slashes.
-    ///
-    /// Valid endpoints may be 'metadata', 'download' or for example
-    /// 'password'.
-    fn api(endpoint: &str, file: &RemoteFile) -> Url {
-        // Get the share URL, and add the secret fragment
-        let mut url = file.url().clone();
-        url.set_path(format!("/api/{}/{}", endpoint, file.id()).as_str());
-        url.set_fragment(None);
-
-        url
-    }
-
-    /// Get the API metadata URL for the given file.
-    pub fn api_metadata(file: &RemoteFile) -> Url {
-        Self::api("metadata", file)
-    }
-
-    /// Get the API download URL for the given file.
-    pub fn api_download(file: &RemoteFile) -> Url {
-        Self::api("download", file)
-    }
-
-    /// Get the API password URL for the given file.
-    pub fn api_password(file: &RemoteFile) -> Url {
-        Self::api("password", file)
-    }
-
-    /// Get the API params URL for the given file.
-    pub fn api_params(file: &RemoteFile) -> Url {
-        Self::api("params", file)
-    }
-
-    /// Get the API info URL for the given file.
-    pub fn api_info(file: &RemoteFile) -> Url {
-        Self::api("info", file)
-    }
-
-    /// Get the API exists URL for the given file.
-    pub fn api_exists(file: &RemoteFile) -> Url {
-        Self::api("exists", file)
-    }
-
-    /// Get the API delete URL for the given file.
-    pub fn api_delete(file: &RemoteFile) -> Url {
-        Self::api("delete", file)
-    }
-}

+ 0 - 19
api/src/config.rs

@@ -1,19 +0,0 @@
-use reqwest::StatusCode;
-
-/// The Send host to use by default.
-pub const SEND_DEFAULT_HOST: &str = "https://send.firefox.com/";
-
-/// The default time after which uploaded files expire after, in seconds.
-pub const SEND_DEFAULT_EXPIRE_TIME: i64 = 24 * 60 * 60;
-
-/// The HTTP status code that is returned for expired or non existant files.
-pub const HTTP_STATUS_EXPIRED: StatusCode = StatusCode::NotFound;
-
-/// The HTTP status code that is returned on authentication failure.
-pub const HTTP_STATUS_UNAUTHORIZED: StatusCode = StatusCode::Unauthorized;
-
-/// The recommended maximum upload size in bytes.
-pub const UPLOAD_SIZE_MAX_RECOMMENDED: u64 = 1024 * 1024 * 1024;
-
-/// The maximum upload size in bytes.
-pub const UPLOAD_SIZE_MAX: u64 = 1024 * 1024 * 1024 * 2;

+ 0 - 33
api/src/crypto/b64.rs

@@ -1,33 +0,0 @@
-//! A simple module for encoding or decoding a base64 string from or to a
-//! byte array.
-//!
-//! This module uses an URL-safe scheme, and doesn't add additional padding
-//! to the encoded strings.
-
-extern crate base64;
-
-pub use self::base64::{
-    CharacterSet,
-    Config,
-    DecodeError,
-    LineEnding,
-    LineWrap,
-};
-
-/// Encode the given byte slice using base64,
-/// in an URL-safe manner without padding.
-pub fn encode(input: &[u8]) -> String {
-    base64::encode_config(input, base64::URL_SAFE_NO_PAD)
-}
-
-/// Decode the given string as base64.
-/// Standard and URL-safe character sets are both supported,
-/// padding is optional.
-pub fn decode(input: &str) -> Result<Vec<u8>, DecodeError> {
-    base64::decode_config(
-        input.replace('+', "-")
-            .replace('/', "_")
-            .trim_right_matches('='),
-        base64::URL_SAFE_NO_PAD,
-    )
-}

+ 0 - 81
api/src/crypto/hdkf.rs

@@ -1,81 +0,0 @@
-extern crate hkdf;
-extern crate sha2;
-
-use self::hkdf::Hkdf;
-use openssl::hash::MessageDigest;
-use openssl::pkcs5::pbkdf2_hmac;
-use self::sha2::Sha256;
-use url::Url;
-
-/// The length of the derived authentication key in bytes.
-const KEY_AUTH_SIZE: usize = 64;
-
-/// The number of iterations to do for deriving a passworded authentication
-/// key.
-const KEY_AUTH_ITERATIONS: usize = 100;
-
-/// Derive a HKDF key.
-///
-/// No _salt_ bytes are used in this function.
-///
-/// # Arguments
-/// * length - Length of the derived key value that is returned.
-/// * ikm - The input keying material.
-/// * info - Optional context and application specific information to use.
-///
-/// # Returns
-/// The output keying material, with the length as as specified in the `length`
-/// argument.
-fn hkdf(
-    length: usize,
-    ikm: &[u8],
-    info: Option<&[u8]>,
-) -> Vec<u8> {
-    // Unwrap info or use empty info
-    let info = info.unwrap_or(&[]);
-
-    // Derive a HKDF key with the given length
-    Hkdf::<Sha256>::new(&ikm, &[])
-        .derive(&info, length)
-}
-
-/// Derive a key to use for file data encryption, based on the given `secret`.
-pub fn derive_file_key(secret: &[u8]) -> Vec<u8> {
-    hkdf(16, secret, Some(b"encryption"))
-}
-
-/// Derive a key to use for metadata encryption, based on the given `secret`.
-pub fn derive_meta_key(secret: &[u8]) -> Vec<u8> {
-    hkdf(16, secret, Some(b"metadata"))
-}
-
-/// Derive a key used for authentication, based on the given `secret`.
-///
-/// A `password` and `url` may be given for special key deriving.
-/// At this time this is not implemented however.
-pub fn derive_auth_key(secret: &[u8], password: Option<&str>, url: Option<&Url>) -> Vec<u8> {
-    // Nothing, or both a password and URL must be given
-    assert_eq!(
-        password.is_none(),
-        url.is_none(),
-        "unable to derive authentication key, missing password or URL",
-    );
-
-    // Derive a key without a password
-    if password.is_none() {
-        return hkdf(KEY_AUTH_SIZE, secret, Some(b"authentication"));
-    }
-
-    // Derive a key with a password and URL
-    // TODO: do not expect/unwrap here
-    let mut key = vec![0u8; KEY_AUTH_SIZE];
-    pbkdf2_hmac(
-        password.unwrap().as_bytes(),
-        url.unwrap().as_str().as_bytes(),
-        KEY_AUTH_ITERATIONS,
-        MessageDigest::sha256(),
-        &mut key,
-    ).expect("failed to derive passworded authentication key");
-
-    key
-}

+ 0 - 153
api/src/crypto/key_set.rs

@@ -1,153 +0,0 @@
-use openssl::symm::Cipher;
-use url::Url;
-
-use api::url::UrlBuilder;
-use file::remote_file::RemoteFile;
-use super::{b64, rand_bytes};
-use super::hdkf::{derive_auth_key, derive_file_key, derive_meta_key};
-
-/// The length of an input vector.
-const KEY_IV_LEN: usize = 12;
-
-pub struct KeySet {
-    /// A secret.
-    secret: Vec<u8>,
-
-    /// Input vector.
-    iv: [u8; KEY_IV_LEN],
-
-    /// A derived file encryption key.
-    file_key: Option<Vec<u8>>,
-
-    /// A derived authentication key.
-    auth_key: Option<Vec<u8>>,
-
-    /// A derived metadata key.
-    meta_key: Option<Vec<u8>>,
-}
-
-impl KeySet {
-    /// Construct a new key, with the given `secret` and `iv`.
-    pub fn new(secret: Vec<u8>, iv: [u8; 12]) -> Self {
-        Self {
-            secret,
-            iv,
-            file_key: None,
-            auth_key: None,
-            meta_key: None,
-        }
-    }
-
-    /// Create a key set from the given file ID and secret.
-    /// This method may be used to create a key set based on a share URL.
-    // TODO: add a parameter for the password and URL
-    // TODO: return a result?
-    // TODO: supply a client instance as parameter
-    pub fn from(file: &RemoteFile, password: Option<&String>) -> Self {
-        // Create a new key set instance
-        let mut set = Self::new(
-            file.secret_raw().clone(),
-            [0; 12],
-        );
-
-        // Derive all keys
-        set.derive();
-
-        // Derive a pasworded key
-        if let Some(password) = password {
-            set.derive_auth_password(password, &UrlBuilder::download(&file, true));
-        }
-
-        set
-    }
-
-    /// Generate a secure new key.
-    ///
-    /// If `derive` is `true`, file, authentication and metadata keys will be
-    /// derived from the generated secret. 
-    pub fn generate(derive: bool) -> Self {
-        // Allocate two keys
-        let mut secret = vec![0u8; 16];
-        let mut iv = [0u8; 12];
-
-        // Generate the secrets
-        rand_bytes(&mut secret)
-            .expect("failed to generate crypto secure random secret");
-        rand_bytes(&mut iv)
-            .expect("failed to generate crypto secure random input vector");
-
-        // Create the key
-        let mut key = Self::new(secret, iv);
-
-        // Derive
-        if derive {
-            key.derive();
-        }
-
-        key
-    }
-
-    /// Derive a file, authentication and metadata key.
-    // TODO: add support for deriving with a password and URL
-    pub fn derive(&mut self) {
-        self.file_key = Some(derive_file_key(&self.secret));
-        self.auth_key = Some(derive_auth_key(&self.secret, None, None));
-        self.meta_key = Some(derive_meta_key(&self.secret));
-    }
-
-    /// Derive an authentication key, with the given password and file URL.
-    /// This method does not derive a (new) file and metadata key.
-    pub fn derive_auth_password(&mut self, pass: &str, url: &Url) {
-        self.auth_key = Some(derive_auth_key(
-            &self.secret,
-            Some(pass),
-            Some(url),
-        ));
-    }
-
-    /// Get the secret key.
-    pub fn secret(&self) -> &[u8] {
-        &self.secret
-    }
-
-    /// Get the secret key as URL-safe base64 encoded string.
-    pub fn secret_encoded(&self) -> String {
-       b64::encode(self.secret())
-    }
-
-    /// Get the input vector.
-    pub fn iv(&self) -> &[u8] {
-        &self.iv
-    }
-
-    /// Set the input vector.
-    pub fn set_iv(&mut self, iv: [u8; KEY_IV_LEN]) {
-        self.iv = iv;
-    }
-
-    /// Get the file encryption key, if derived.
-    pub fn file_key(&self) -> Option<&Vec<u8>> {
-        self.file_key.as_ref()
-    }
-
-    /// Get the authentication encryption key, if derived.
-    pub fn auth_key(&self) -> Option<&Vec<u8>> {
-        self.auth_key.as_ref()
-    }
-
-    /// Get the authentication encryption key, if derived,
-    /// as URL-safe base64 encoded string.
-    pub fn auth_key_encoded(&self) -> Option<String> {
-        self.auth_key().map(|key| b64::encode(key))
-    }
-
-    /// Get the metadata encryption key, if derived.
-    pub fn meta_key(&self) -> Option<&Vec<u8>> {
-        self.meta_key.as_ref()
-    }
-
-    /// Get the cipher type to use in combination with these keys.
-    pub fn cipher() -> Cipher {
-        Cipher::aes_128_gcm()
-    }
-}

+ 0 - 7
api/src/crypto/mod.rs

@@ -1,7 +0,0 @@
-pub mod b64;
-pub mod hdkf;
-pub mod key_set;
-pub mod sig;
-
-// Reexport the cryptographically secure random bytes generator
-pub use super::openssl::rand::rand_bytes;

+ 0 - 34
api/src/crypto/sig.rs

@@ -1,34 +0,0 @@
-use openssl::error::ErrorStack;
-use openssl::hash::MessageDigest;
-use openssl::pkey::PKey;
-use openssl::sign::Signer;
-
-use super::b64;
-
-/// Compute the signature for the given data and key.
-/// This is done using an HMAC key using the SHA256 digest.
-///
-/// If computing the signature failed, an error is returned.
-pub fn signature(key: &[u8], data: &[u8]) -> Result<Vec<u8>, ErrorStack> {
-    // Build the key, and signer
-    let pkey = PKey::hmac(&key)?;
-    let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?;
-
-    // Feed the data
-    signer.update(&data)?;
-
-    // Compute the signature
-    Ok(signer.sign_to_vec()?)
-}
-
-/// Compute the signature for the given data and key.
-/// This is done using an HMAC key using the SHA256 digest.
-///
-/// The resulting signature is encoded as base64 string in an URL-safe manner.
-///
-/// If computing the signature failed, an error is returned.
-pub fn signature_encoded(key: &[u8], data: &[u8])
-    -> Result<String, ErrorStack>
-{
-    signature(key, data).map(|sig| b64::encode(&sig))
-}

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

@@ -1 +0,0 @@
-pub mod status_code;

+ 0 - 15
api/src/ext/status_code.rs

@@ -1,15 +0,0 @@
-use reqwest::StatusCode;
-
-/// Reqwest status code extention, to easily retrieve an error message.
-pub 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| format!("{} {}", self.as_u16(), text))
-            .unwrap_or_else(|| format!("{}", self.as_u16()))
-    }
-}

+ 0 - 117
api/src/file/metadata.rs

@@ -1,117 +0,0 @@
-extern crate hyper;
-
-use std::fmt;
-
-use mime_guess::Mime;
-use reqwest::header::{
-    Formatter as HeaderFormatter,
-    Header,
-    Raw,
-};
-use self::hyper::error::Error as HyperError;
-use serde_json;
-
-use crypto::b64;
-
-/// The MIME type string for a tar file.
-const MIME_TAR: &str = "application/x-tar";
-
-/// File metadata, which is send to the server.
-#[derive(Debug, Serialize, Deserialize)]
-pub struct Metadata {
-    /// The input vector.
-    iv: String,
-
-    /// The file name.
-    name: String,
-
-    /// The file mimetype.
-    #[serde(rename="type")]
-    mime: String,
-}
-
-impl Metadata {
-    /// Construct metadata from the given properties.
-    ///
-    /// Parameters:
-    /// * iv: initialisation vector
-    /// * name: file name
-    /// * mime: file mimetype
-    pub fn from(iv: &[u8], name: String, mime: &Mime) -> Self {
-        Metadata {
-            iv: b64::encode(iv),
-            name,
-            mime: mime.to_string(),
-        }
-    }
-
-    /// Convert this structure to a JSON string.
-    pub fn to_json(&self) -> String {
-        serde_json::to_string(&self).unwrap()
-    }
-
-    /// Get the file name.
-    pub fn name(&self) -> &str {
-        &self.name
-    }
-
-    /// Get the file MIME type.
-    pub fn mime(&self) -> &str {
-        &self.mime
-    }
-
-    /// Get the input vector
-    // TODO: use an input vector length from a constant
-    pub fn iv(&self) -> [u8; 12] {
-        // Decode the input vector
-        let decoded = b64::decode(&self.iv).unwrap();
-
-        // Create a sized array
-        *array_ref!(decoded, 0, 12)
-    }
-
-    /**
-     * Check whether this MIME type is recognized as supported archive type.
-     * `true` is returned if it's an archive, `false` if not.
-     */
-    pub fn is_archive(&self) -> bool {
-        self.mime.to_lowercase() == MIME_TAR.to_lowercase()
-    }
-}
-
-/// A X-File-Metadata header for reqwest, that is used to pass encrypted
-/// metadata to the server.
-///
-/// The encrypted metadata (bytes) is base64 encoded when constructing this
-/// header using `from`.
-#[derive(Clone)]
-pub struct XFileMetadata {
-    /// The metadata, as a base64 encoded string.
-    metadata: String,
-}
-
-impl XFileMetadata {
-    /// Construct the header from the given encrypted metadata.
-    pub fn from(bytes: &[u8]) -> Self {
-        XFileMetadata {
-            metadata: b64::encode(bytes),
-        }
-    }
-}
-
-/// Make this struct usable as reqwest header.
-impl Header for XFileMetadata {
-    fn header_name() -> &'static str {
-        "X-File-Metadata"
-    }
-
-    fn parse_header(_raw: &Raw) -> Result<Self, HyperError> {
-        // TODO: implement this some time
-        unimplemented!();
-    }
-
-    fn fmt_header(&self, f: &mut HeaderFormatter) -> fmt::Result {
-        // TODO: is this encoding base64 for us?
-        f.fmt_line(&self.metadata)
-    }
-}

+ 0 - 2
api/src/file/mod.rs

@@ -1,2 +0,0 @@
-pub mod remote_file;
-pub mod metadata;

+ 0 - 339
api/src/file/remote_file.rs

@@ -1,339 +0,0 @@
-extern crate chrono;
-extern crate regex;
-
-use api::url::UrlBuilder;
-use url::{
-    ParseError as UrlParseError,
-    Url,
-};
-use self::chrono::{DateTime, Duration, Utc};
-use self::regex::Regex;
-use url_serde;
-
-use config::SEND_DEFAULT_EXPIRE_TIME;
-use crypto::b64;
-
-/// A pattern for share URL paths, capturing the file ID.
-// TODO: match any sub-path?
-// TODO: match URL-safe base64 chars for the file ID?
-// TODO: constrain the ID length?
-const SHARE_PATH_PATTERN: &str = r"^/?download/([[:alnum:]]{8,}={0,3})/?$";
-
-/// A pattern for share URL fragments, capturing the file secret.
-// TODO: constrain the secret length?
-const SHARE_FRAGMENT_PATTERN: &str = r"^([a-zA-Z0-9-_+/]+)?\s*$";
-
-/// A struct representing an uploaded file on a Send host.
-///
-/// The struct contains the file ID, the file URL, the key that is required
-/// in combination with the file, and the owner key.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct RemoteFile {
-    /// The ID of the file on that server.
-    id: String,
-
-    /// The time the file was uploaded at, if known.
-    upload_at: Option<DateTime<Utc>>,
-
-    /// The time the file will expire at.
-    expire_at: DateTime<Utc>,
-
-    /// Define whether the expiry time is uncertain.
-    expire_uncertain: bool,
-
-    /// The host the file was uploaded to.
-    #[serde(with = "url_serde")]
-    host: Url,
-
-    /// The file URL that was provided by the server.
-    #[serde(with = "url_serde")]
-    url: Url,
-
-    /// The secret key that is required to download the file.
-    secret: Vec<u8>,
-
-    /// The owner key, that can be used to manage the file on the server.
-    owner_token: Option<String>,
-}
-
-impl RemoteFile {
-    /// Construct a new file.
-    pub fn new(
-        id: String,
-        upload_at: Option<DateTime<Utc>>,
-        expire_at: Option<DateTime<Utc>>,
-        host: Url,
-        url: Url,
-        secret: Vec<u8>,
-        owner_token: Option<String>,
-    ) -> Self {
-        // Assign the default expiry time if uncetain
-        let expire_uncertain = expire_at.is_none();
-        let expire_at = expire_at.unwrap_or(
-            Utc::now() + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME)
-        );
-
-        // Build the object
-        Self {
-            id,
-            upload_at,
-            expire_at,
-            expire_uncertain,
-            host,
-            url,
-            secret,
-            owner_token,
-        }
-    }
-
-    /// Construct a new file, that was created at this exact time.
-    /// This will set the file expiration time 
-    pub fn new_now(
-        id: String,
-        host: Url,
-        url: Url,
-        secret: Vec<u8>,
-        owner_token: Option<String>,
-    ) -> Self {
-        // Get the current time
-        let now = Utc::now();
-        let expire_at = now + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME);
-
-        // Construct and return
-        Self::new(
-            id,
-            Some(now),
-            Some(expire_at),
-            host,
-            url,
-            secret,
-            owner_token,
-        )
-    }
-
-    /// Try to parse the given share URL.
-    ///
-    /// The given URL is matched against a share URL pattern,
-    /// this does not check whether the host is a valid and online host.
-    ///
-    /// If the URL fragmet contains a file secret, it is also parsed.
-    /// If it does not, the secret is left empty and must be specified
-    /// manually.
-    ///
-    /// An optional owner token may be given.
-    pub fn parse_url(url: Url, owner_token: Option<String>)
-        -> Result<RemoteFile, FileParseError>
-    {
-        // Build the host
-        let mut host = url.clone();
-        host.set_fragment(None);
-        host.set_query(None);
-        host.set_path("");
-
-        // Validate the path, get the file ID
-        let re_path = Regex::new(SHARE_PATH_PATTERN).unwrap();
-        let id = re_path.captures(url.path())
-            .ok_or(FileParseError::InvalidUrl)?[1]
-            .trim()
-            .to_owned();
-
-        // Get the file secret
-        let mut secret = Vec::new();
-        if let Some(fragment) = url.fragment() {
-            let re_fragment = Regex::new(SHARE_FRAGMENT_PATTERN).unwrap();
-            if let Some(raw) = re_fragment.captures(fragment)
-                .ok_or(FileParseError::InvalidSecret)?
-                .get(1)
-            {
-                secret = b64::decode(raw.as_str().trim())
-                        .map_err(|_| FileParseError::InvalidSecret)?
-            }
-        }
-
-        // Construct the file
-        Ok(Self::new(
-            id,
-            None,
-            None,
-            host,
-            url,
-            secret,
-            owner_token,
-        ))
-    }
-
-    /// Get the file ID.
-    pub fn id(&self) -> &str {
-        &self.id
-    }
-
-    /// Get the time the file will expire after.
-    /// Note that this time may not be correct as it may have been guessed,
-    /// see `expire_uncertain()`.
-    pub fn expire_at(&self) -> DateTime<Utc> {
-        self.expire_at
-    }
-
-    /// Get the duration the file will expire after.
-    /// Note that this time may not be correct as it may have been guessed,
-    /// see `expire_uncertain()`.
-    pub fn expire_duration(&self) -> Duration {
-        // Get the current time
-        let now = Utc::now();
-
-        // Return the duration if not expired, otherwise return zero
-        if self.expire_at > now {
-            self.expire_at - now
-        } else {
-            Duration::zero()
-        }
-    }
-
-    /// Set the time this file will expire at.
-    /// None may be given to assign the default expiry time with the
-    /// uncertainty flag set.
-    pub fn set_expire_at(&mut self, expire_at: Option<DateTime<Utc>>) {
-        if let Some(expire_at) = expire_at {
-            self.expire_at = expire_at;
-        } else {
-            self.expire_at = Utc::now() + Duration::seconds(SEND_DEFAULT_EXPIRE_TIME);
-            self.expire_uncertain = true;
-        }
-    }
-
-    /// Set the time this file will expire at,
-    /// based on the given duration from now.
-    pub fn set_expire_duration(&mut self, duration: Duration) {
-        self.set_expire_at(Some(Utc::now() + duration));
-    }
-
-    /// Check whether this file has expired, based on it's expiry property.
-    pub fn has_expired(&self) -> bool {
-        self.expire_at < Utc::now()
-    }
-
-    /// Check whehter the set expiry time is uncertain.
-    /// If the expiry time of a file is unknown,
-    /// the default time is assigned from the first time
-    /// the file was used. Such time will be uncertain as it probably isn't
-    /// correct.
-    /// This time may be used however to check for expiry.
-    pub fn expire_uncertain(&self) -> bool {
-        self.expire_uncertain
-    }
-
-    /// Get the file URL, provided by the server.
-    pub fn url(&self) -> &Url {
-        &self.url
-    }
-
-    /// Get the raw secret.
-    // TODO: ensure whether the secret is set?
-    pub fn secret_raw(&self) -> &Vec<u8> {
-        &self.secret
-    }
-
-    /// Get the secret as base64 encoded string.
-    pub fn secret(&self) -> String {
-        b64::encode(self.secret_raw())
-    }
-
-    /// Set the secret for this file.
-    pub fn set_secret(&mut self, secret: Vec<u8>) {
-        self.secret = secret;
-    }
-
-    /// Check whether a file secret is set.
-    /// This secret must be set to decrypt a downloaded Send file.
-    pub fn has_secret(&self) -> bool {
-        !self.secret.is_empty()
-    }
-
-    /// Get the owner token if set.
-    pub fn owner_token(&self) -> Option<&String> {
-        self.owner_token.as_ref()
-    }
-
-    /// Get the owner token if set.
-    pub fn owner_token_mut(&mut self) -> &mut Option<String> {
-        &mut self.owner_token
-    }
-
-    /// Set the owner token, wrapped in an option.
-    /// If `None` is given, the owner token will be unset.
-    pub fn set_owner_token(&mut self, token: Option<String>) {
-        self.owner_token = token;
-    }
-
-    /// Check whether an owner token is set in this remote file.
-    pub fn has_owner_token(&self) -> bool {
-        self.owner_token
-            .clone()
-            .map(|t| !t.is_empty())
-            .unwrap_or(false)
-    }
-
-    /// Get the host URL for this remote file.
-    pub fn host(&self) -> Url {
-        self.host.clone()
-    }
-
-    /// Build the download URL of the given file.
-    /// This URL is identical to the share URL, a term used in this API.
-    /// Set `secret` to `true`, to include it in the URL if known.
-    pub fn download_url(&self, secret: bool) -> Url {
-        UrlBuilder::download(&self, secret)
-    }
-
-    /// Merge properties non-existant into this file, from the given other file.
-    /// This is ofcourse only done for properties that may be empty.
-    ///
-    /// The file IDs are not asserted for equality.
-    #[allow(unknown_lints, useless_let_if_seq)]
-    pub fn merge(&mut self, other: &RemoteFile, overwrite: bool) -> bool {
-        // Remember whether anything has changed
-        let mut changed = false;
-
-        // Set the upload time
-        if other.upload_at.is_some() && (self.upload_at.is_none() || overwrite) {
-            self.upload_at = other.upload_at;
-            changed = true;
-        }
-
-        // Set the expire time
-        if !other.expire_uncertain() && (self.expire_uncertain() || overwrite) {
-            self.expire_at = other.expire_at;
-            self.expire_uncertain = other.expire_uncertain();
-            changed = true;
-        }
-
-        // Set the secret
-        if other.has_secret() && (!self.has_secret() || overwrite) {
-            self.secret = other.secret_raw().clone();
-            changed = true;
-        }
-
-        // Set the owner token
-        if other.owner_token.is_some() && (self.owner_token.is_none() || overwrite) {
-            self.owner_token = other.owner_token.clone();
-            changed = true;
-        }
-
-        changed
-    }
-}
-
-#[derive(Debug, Fail)]
-pub enum FileParseError {
-    /// An URL format error.
-    #[fail(display = "failed to parse remote file, invalid URL format")]
-    UrlFormatError(#[cause] UrlParseError),
-
-    /// An error for an invalid share URL format.
-    #[fail(display = "failed to parse remote file, invalid URL")]
-    InvalidUrl,
-
-    /// An error for an invalid secret format, if an URL fragmet exists.
-    #[fail(display = "failed to parse remote file, invalid secret in URL")]
-    InvalidSecret,
-}

+ 0 - 27
api/src/lib.rs

@@ -1,27 +0,0 @@
-#[macro_use]
-extern crate arrayref;
-#[macro_use]
-extern crate derive_builder;
-extern crate failure;
-#[macro_use]
-extern crate failure_derive;
-extern crate mime_guess;
-extern crate openssl;
-pub extern crate reqwest;
-extern crate serde;
-#[macro_use]
-extern crate serde_derive;
-extern crate serde_json;
-extern crate time;
-pub extern crate url;
-pub extern crate url_serde;
-
-pub mod action;
-mod api;
-pub mod config;
-pub mod crypto;
-mod ext;
-pub mod file;
-pub mod reader;
-
-pub use failure::Error;

+ 0 - 617
api/src/reader.rs

@@ -1,617 +0,0 @@
-use std::cmp::{max, min};
-use std::fs::File;
-use std::io::{
-    self,
-    BufReader,
-    Cursor,
-    Error as IoError,
-    Read,
-    Write,
-};
-use std::sync::{Arc, Mutex};
-
-use openssl::symm::{
-    Cipher,
-    Crypter,
-    Mode as CrypterMode,
-};
-
-/// The length in bytes of crytographic tags that are used.
-const TAG_LEN: usize = 16;
-
-// TODO: create a generic reader/writer wrapper for the the encryptor/decryptor.
-
-/// A lazy file reader, that encrypts the file with the given `cipher`
-/// and appends the cryptographic tag to the end of it.
-///
-/// This reader is lazy because the file data loaded from the system
-/// and encrypted when it is read from the reader.
-/// This greatly reduces memory usage for large files.
-///
-/// This reader encrypts the file data with an appended cryptographic tag.
-///
-/// The reader uses a small internal buffer as data is encrypted in blocks,
-/// which may output more data than fits in the given buffer while reading.
-/// The excess data is then returned on the next read.
-pub struct EncryptedFileReader {
-    /// The raw file that is read from.
-    file: File,
-
-    /// The cipher type used for encrypting.
-    cipher: Cipher,
-
-    /// The crypter used for encrypting the read file.
-    crypter: Crypter,
-
-    /// A tag cursor that reads the tag to append,
-    /// when the file is fully read and the tag is known.
-    tag: Option<Cursor<Vec<u8>>>,
-
-    /// The internal buffer, containing encrypted data that has yet to be
-    /// outputted to the reader. This data is always outputted before any new
-    /// data is produced.
-    internal_buf: Vec<u8>,
-}
-
-impl EncryptedFileReader {
-    /// Construct a new reader for the given `file` with the given `cipher`.
-    ///
-    /// This method consumes twice the size of the file in memory while
-    /// constructing, and constructs a reader that has a size similar to the
-    /// file.
-    ///
-    /// It is recommended to wrap this reader in some sort of buffer, such as:
-    /// `std::io::BufReader`
-    pub fn new(file: File, cipher: Cipher, key: &[u8], iv: &[u8])
-        -> Result<Self, io::Error>
-    {
-        // Build the crypter
-        let crypter = Crypter::new(
-            cipher,
-            CrypterMode::Encrypt,
-            key,
-            Some(iv),
-        )?;
-
-        // Construct the encrypted reader
-        Ok(
-            EncryptedFileReader {
-                file,
-                cipher,
-                crypter,
-                tag: None,
-                internal_buf: Vec::new(),
-            }
-        )
-    }
-
-    /// Read data from the internal buffer if there is any data in it, into
-    /// the given `buf`.
-    ///
-    /// The number of bytes that were read into `buf` is returned.
-    ///
-    /// If there is no data to be read, or `buf` has a zero size, `0` is always
-    /// returned.
-    fn read_internal(&mut self, buf: &mut [u8]) -> usize {
-        // Return if there is no data to read
-        if self.internal_buf.is_empty() || buf.is_empty() {
-            return 0;
-        }
-
-        // Determine how much data will be read
-        let len = min(buf.len(), self.internal_buf.len());
-
-        // Slice the section we will read from, copy to the reader
-        {
-            let (out, _) = self.internal_buf.split_at(len);
-            let (buf, _) = buf.split_at_mut(len);
-            buf.copy_from_slice(out);
-        }
-
-        // Drain the read data from the internal buffer
-        self.internal_buf.drain(..len);
-
-        len
-    }
-
-    /// Read data directly from the file, and encrypt it.
-    ///
-    /// Because data may be encrypted in blocks, it is possible more data
-    /// is produced than fits in the given `buf`. In that case the excess data
-    /// is stored in an internal buffer, and is ouputted the next time being
-    /// read from the reader.
-    ///
-    /// The number of bytes that is read into `buf` is returned.
-    fn read_file_encrypted(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
-        // Get the block size, determine the buffer size, create a data buffer
-        let block_size = self.cipher.block_size();
-        let mut data = vec![0u8; buf.len()];
-
-        // Read the file, return if nothing was read
-        let len = self.file.read(&mut data)?;
-        if len == 0 {
-            return Ok(0);
-        }
-
-        // Create an encrypted buffer, truncate the data buffer
-        let mut encrypted = vec![0u8; len + block_size];
-
-        // Encrypt the data that was read
-        let len = self.crypter.update(&data[..len], &mut encrypted)?;
-
-        // Calculate how many bytes will be copied to the reader
-        let out_len = min(buf.len(), len);
-
-        // Fill the reader buffer
-        let (out, remaining) = encrypted.split_at(out_len);
-        let (buf, _) = buf.split_at_mut(out_len);
-        buf.copy_from_slice(out);
-
-        // Splice to the actual remaining bytes, store it for later
-        let (store, _) = remaining.split_at(len - out_len);
-        self.internal_buf.extend(store.iter());
-
-        // Return the number of bytes read to the reader
-        Ok(out_len)
-    }
-
-    /// Finalize the crypter once it is done encrypthing the whole file.
-    /// This finalization step produces a tag that is placed after the
-    /// encrypted file data.
-    ///
-    /// This step must be invoked to start reading the tag,
-    /// and after it has been invoked no data must be encrypted anymore.
-    ///
-    /// This method must only be invoked once.
-    fn finalize_file(&mut self) -> Result<(), io::Error> {
-        // Finalize the crypter, catch any remaining output
-        let mut output = vec![0u8; self.cipher.block_size()];
-        let len = self.crypter.finalize(&mut output)?;
-
-        // Move additional output in the internal buffer
-        if len > 0 {
-            self.internal_buf.extend(output.iter().take(len));
-        }
-
-        // Fetch the encryption tag, and create an internal reader for it
-        let mut tag = vec![0u8; TAG_LEN];
-        self.crypter.get_tag(&mut tag)?;
-        self.tag = Some(Cursor::new(tag));
-
-        Ok(())
-    }
-}
-
-impl ExactLengthReader for EncryptedFileReader {
-    /// Calculate the total length of the encrypted file with the appended
-    /// tag.
-    /// Useful in combination with some progress monitor, to determine how much
-    /// of the file is read or for example; sent over the network.
-    fn len(&self) -> Result<u64, io::Error> {
-        Ok(self.file.metadata()?.len() + TAG_LEN as u64)
-    }
-}
-
-/// The reader trait implementation.
-impl Read for EncryptedFileReader {
-    /// Read from the encrypted file, and then the encryption tag.
-    fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
-        // Read from the internal buffer, return full or splice to empty
-        let len = self.read_internal(buf);
-        if len >= buf.len() {
-            return Ok(len);
-        }
-        let (_, buf) = buf.split_at_mut(len);
-
-        // Keep track of the total number of read bytes, to return
-        let mut total = len;
-
-        // If the tag reader has been created, only read from that one
-        if let Some(ref mut tag) = self.tag {
-            return Ok(tag.read(buf)? + total);
-        }
-
-        // Read the encrypted file, return full or splice to empty
-        let len = self.read_file_encrypted(buf)?;
-        total += len;
-        if len >= buf.len() {
-            return Ok(total);
-        }
-        let (_, buf) = buf.split_at_mut(len);
-
-        // Finalize the file crypter, and build the tag
-        self.finalize_file()?;
-
-        // Try to fill the remaining part of the buffer
-        Ok(self.read(buf)? + total)
-    }
-}
-
-// TODO: implement this some other way
-unsafe impl Send for EncryptedFileReader {}
-
-/// A reader wrapper, that measures the reading process for a reader with a
-/// known length.
-///
-/// If the reader exceeds the initially specified length,
-/// the reader will continue to allow reads.
-/// The length property will grow accordingly.
-///
-/// The reader will only start producing `None` if the wrapped reader is doing
-/// so.
-pub struct ProgressReader<R> {
-    /// The wrapped reader.
-    inner: R,
-
-    /// The total length of the reader.
-    len: u64,
-
-    /// The current reading progress.
-    progress: u64,
-
-    /// A reporter, to report the progress status to.
-    reporter: Option<Arc<Mutex<ProgressReporter>>>,
-}
-
-impl<R: Read> ProgressReader<R> {
-    /// Wrap the given reader with an exact length, in a progress reader.
-    pub fn new(inner: R) -> Result<Self, IoError>
-        where
-            R: ExactLengthReader
-    {
-        Ok(
-            Self {
-                len: inner.len()?,
-                inner,
-                progress: 0,
-                reporter: None,
-            }
-        )
-    }
-
-    /// Wrap the given reader with the given length in a progress reader.
-    pub fn from(inner: R, len: u64) -> Self {
-        Self {
-            inner,
-            len,
-            progress: 0,
-            reporter: None,
-        }
-    }
-
-    /// Set the reporter to report the status to.
-    pub fn set_reporter(&mut self, reporter: Arc<Mutex<ProgressReporter>>) {
-        self.reporter = Some(reporter);
-    }
-
-    /// Get the current progress.
-    pub fn progress(&self) -> u64 {
-        self.progress
-    }
-}
-
-impl<R: Read> Read for ProgressReader<R> {
-    /// Read from the encrypted file, and then the encryption tag.
-    fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
-        // Read from the wrapped reader, increase the progress
-        let len = self.inner.read(buf)?;
-        self.progress += len as u64;
-
-        // Keep the specified length in-bound
-        if self.progress > self.len {
-            self.len = self.progress;
-        }
-
-        // Report
-        if let Some(reporter) = self.reporter.as_mut() {
-            let progress = self.progress;
-            let _ = reporter.lock().map(|mut r| r.progress(progress));
-        }
-
-        Ok(len)
-    }
-}
-
-impl<R: Read> ExactLengthReader for ProgressReader<R> {
-    // Return the specified length.
-    fn len(&self) -> Result<u64, io::Error> {
-        Ok(self.len)
-    }
-}
-
-/// A progress reporter.
-pub trait ProgressReporter: Send {
-    /// Start the progress with the given total.
-    fn start(&mut self, total: u64);
-
-    /// A progress update.
-    fn progress(&mut self, progress: u64);
-
-    /// Finish the progress.
-    fn finish(&mut self);
-}
-
-/// A trait for readers, to get the exact length of a reader.
-pub trait ExactLengthReader {
-    /// Get the exact length of the reader in bytes.
-    fn len(&self) -> Result<u64, io::Error>;
-
-    /// Check whehter this extact length reader is emtpy.
-    fn is_empty(&self) -> Result<bool, io::Error> {
-        self.len().map(|l| l == 0)
-    }
-}
-
-impl<R: ExactLengthReader + Read> ExactLengthReader for BufReader<R> {
-    fn len(&self) -> Result<u64, io::Error> {
-        self.get_ref().len()
-    }
-}
-
-/// A lazy file writer, that decrypt the file with the given `cipher`
-/// and verifies it with the tag appended to the end of the input data.
-///
-/// This writer is lazy because the input data is decrypted and written to the
-/// specified file on the fly, instead of buffering all the data first.
-/// This greatly reduces memory usage for large files.
-///
-/// The length of the input data (including the appended tag) must be given
-/// when this reader is initialized. When all data including the tag is read,
-/// the decrypted data is verified with the tag. If the tag doesn't match the
-/// decrypted data, a write error is returned on the last write.
-/// This writer will never write more bytes than the length initially
-/// specified.
-///
-/// This reader encrypts the input data with the given key and input vector.
-///
-/// A failed writing implies that no data could be written, or that the data
-/// wasn't successfully decrypted because of an decryption or tag matching
-/// error. Such a fail means that the file will be incomplete or corrupted,
-/// and should therefore be removed from the disk.
-///
-/// It is highly recommended to invoke the `verified()` method after writing
-/// the file, to ensure the written file is indeed complete and fully verified.
-pub struct EncryptedFileWriter {
-    /// The file to write the decrypted data to.
-    file: File,
-
-    /// The number of bytes that have currently been written to this writer.
-    cur: usize,
-
-    /// The length of all the data, which includes the file data and the
-    /// appended tag.
-    len: usize,
-
-    /// The cipher type used for decrypting.
-    cipher: Cipher,
-
-    /// The crypter used for decrypting the data.
-    crypter: Crypter,
-
-    /// A buffer for the tag.
-    tag_buf: Vec<u8>,
-
-    /// A boolean that defines whether the decrypted data has successfully
-    /// been verified.
-    verified: bool,
-}
-
-impl EncryptedFileWriter {
-    /// Construct a new encrypted file writer.
-    ///
-    /// The file to write to must be given to `file`, which must be open for
-    /// writing. The total length of the input data in bytes must be given to
-    /// `len`, which includes both the file bytes and the appended tag.
-    ///
-    /// For decryption, a `cipher`, `key` and `iv` must also be given.
-    pub fn new(file: File, len: usize, cipher: Cipher, key: &[u8], iv: &[u8])
-        -> Result<Self, io::Error>
-    {
-        // Build the crypter
-        let crypter = Crypter::new(
-            cipher,
-            CrypterMode::Decrypt,
-            key,
-            Some(iv),
-        )?;
-
-        // Construct the encrypted reader
-        Ok(
-            EncryptedFileWriter {
-                file,
-                cur: 0,
-                len,
-                cipher,
-                crypter,
-                tag_buf: Vec::with_capacity(TAG_LEN),
-                verified: false,
-            }
-        )
-    }
-
-    /// Check wheher the complete tag is buffered.
-    pub fn has_tag(&self) -> bool {
-        self.tag_buf.len() >= TAG_LEN
-    }
-
-    /// Check whether the decrypted data is succesfsully verified.
-    ///
-    /// If this method returns true the following is implied:
-    /// - The complete file has been written.
-    /// - The complete file was successfully decrypted.
-    /// - The included tag matches the decrypted file.
-    ///
-    /// It is highly recommended to invoke this method and check the
-    /// verification after writing the file using this writer.
-    pub fn verified(&self) -> bool {
-        self.verified
-    }
-}
-
-impl ExactLengthReader for EncryptedFileWriter {
-    fn len(&self) -> Result<u64, IoError> {
-        Ok(self.len as u64)
-    }
-}
-
-/// The writer trait implementation.
-impl Write for EncryptedFileWriter {
-    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
-        // Do not write anything if the tag was already written
-        if self.verified() || self.has_tag() {
-            return Ok(0);
-        }
-
-        // Determine how many file and tag bytes we still need to process
-        let file_bytes = max(self.len - TAG_LEN - self.cur, 0);
-        let tag_bytes = TAG_LEN - self.tag_buf.len();
-
-        // Split the input buffer
-        let (file_buf, tag_buf) = buf.split_at(min(file_bytes, buf.len()));
-
-        // Read from the file buf
-        if !file_buf.is_empty() {
-            // Create a decrypted buffer, with the proper size
-            let block_size = self.cipher.block_size();
-            let mut decrypted = vec![0u8; file_bytes + block_size];
-
-            // Decrypt bytes
-            // TODO: catch error in below statement
-            let len = self.crypter.update(
-                file_buf,
-                &mut decrypted,
-            )?;
-
-            // Write to the file
-            self.file.write_all(&decrypted[..len])?;
-        }
-
-        // Read from the tag part to fill the tag buffer
-        if !tag_buf.is_empty() {
-            self.tag_buf.extend(tag_buf.iter().take(tag_bytes));
-        }
-
-        // Verify the tag once it has been buffered completely
-        if self.has_tag() {
-            // Set the tag
-            self.crypter.set_tag(&self.tag_buf)?;
-
-            // Create a buffer for any remaining data
-            let block_size = self.cipher.block_size();
-            let mut extra = vec![0u8; block_size];
-
-            // Finalize, write all remaining data
-            let len = self.crypter.finalize(&mut extra)?;
-            self.file.write_all(&extra[..len])?;
-
-            // Set the verified flag
-            self.verified = true;
-        }
-
-        // Compute how many bytes were written
-        let len = file_buf.len() + min(tag_buf.len(), TAG_LEN);
-        self.cur += len;
-        Ok(len)
-    }
-
-    fn flush(&mut self) -> Result<(), io::Error> {
-        self.file.flush()
-    }
-}
-
-/// A writer wrapper, that measures the reading process for a writer with a
-/// known length.
-///
-/// If the writer exceeds the initially specified length,
-/// the writer will continue to allow reads.
-/// The length property will grow accordingly.
-///
-/// The writer will only start producing `None` if the wrapped writer is doing
-/// so.
-pub struct ProgressWriter<W> {
-    /// The wrapped writer.
-    inner: W,
-
-    /// The total length of the writer.
-    len: u64,
-
-    /// The current reading progress.
-    progress: u64,
-
-    /// A reporter, to report the progress status to.
-    reporter: Option<Arc<Mutex<ProgressReporter>>>,
-}
-
-impl<W: Write> ProgressWriter<W> {
-    /// Wrap the given writer with an exact length, in a progress writer.
-    pub fn new(inner: W) -> Result<Self, IoError>
-        where
-            W: ExactLengthReader
-    {
-        Ok(
-            Self {
-                len: inner.len()?,
-                inner,
-                progress: 0,
-                reporter: None,
-            }
-        )
-    }
-
-    /// Wrap the given writer with the given length in a progress writer.
-    pub fn from(inner: W, len: u64) -> Self {
-        Self {
-            inner,
-            len,
-            progress: 0,
-            reporter: None,
-        }
-    }
-
-    /// Set the reporter to report the status to.
-    pub fn set_reporter(&mut self, reporter: Arc<Mutex<ProgressReporter>>) {
-        self.reporter = Some(reporter);
-    }
-
-    /// Get the current progress.
-    pub fn progress(&self) -> u64 {
-        self.progress
-    }
-
-    /// Unwrap the inner from the progress writer.
-    pub fn unwrap(self) -> W {
-        self.inner
-    }
-}
-
-impl<W: Write> Write for ProgressWriter<W> {
-    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
-        // Write from the wrapped writer, increase the progress
-        let len = self.inner.write(buf)?;
-        self.progress += len as u64;
-
-        // Keep the specified length in-bound
-        if self.progress > self.len {
-            self.len = self.progress;
-        }
-
-        // Report
-        if let Some(reporter) = self.reporter.as_mut() {
-            let progress = self.progress;
-            let _ = reporter.lock().map(|mut r| r.progress(progress));
-        }
-
-        Ok(len)
-    }
-
-    fn flush(&mut self) -> Result<(), IoError> {
-        self.inner.flush()
-    }
-}
-
-impl<W: Write> ExactLengthReader for ProgressWriter<W> {
-    // Return the specified length.
-    fn len(&self) -> Result<u64, io::Error> {
-        Ok(self.len)
-    }
-}

+ 0 - 49
cli/Cargo.toml

@@ -1,49 +0,0 @@
-[package]
-name = "ffsend"
-description = """\
-    Easily and securely share files from the command line.\n\
-    A fully featured Firefox Send client.\
-"""
-version = "0.0.1"
-authors = ["Tim Visee <https://timvisee.com/>"]
-workspace = ".."
-
-[[bin]]
-path = "src/main.rs"
-name = "ffsend"
-
-[features]
-default = ["archive", "clipboard", "history"]
-
-# Compile with file archiving support
-archive = ["tar"]
-
-# Compile with file history support
-history = []
-
-# Compile without colored output support
-no-color = ["colored/no-color"]
-
-[dependencies]
-chrono = "0.4"
-clap = "2.31"
-colored = "1.6"
-derive_builder = "0.5"
-directories = "0.10"
-failure = "0.1"
-ffsend-api = { version = "*", path = "../api" }
-fs2 = "0.4"
-lazy_static = "1.0"
-open = "1"
-pbr = "1"
-prettytable-rs = "0.6"
-rpassword = "2.0"
-serde = "1.0"
-serde_derive = "1.0"
-tar = { version = "0.4", optional = true }
-tempfile = "3"
-toml = "0.4"
-version-compare = "0.0.6"
-
-[target.'cfg(not(target_os = "linux"))'.dependencies]
-clipboard = { version = "0.4", optional = true }

+ 0 - 0
cli/src/action/debug.rs → src/action/debug.rs


+ 0 - 0
cli/src/action/delete.rs → src/action/delete.rs


+ 0 - 0
cli/src/action/download.rs → src/action/download.rs


+ 0 - 0
cli/src/action/exists.rs → src/action/exists.rs


+ 0 - 0
cli/src/action/history.rs → src/action/history.rs


+ 0 - 0
cli/src/action/info.rs → src/action/info.rs


+ 0 - 0
cli/src/action/mod.rs → src/action/mod.rs


+ 0 - 0
cli/src/action/params.rs → src/action/params.rs


+ 0 - 0
cli/src/action/password.rs → src/action/password.rs


+ 0 - 0
cli/src/action/upload.rs → src/action/upload.rs


+ 0 - 0
cli/src/archive/archive.rs → src/archive/archive.rs


+ 0 - 0
cli/src/archive/archiver.rs → src/archive/archiver.rs


+ 0 - 0
cli/src/archive/mod.rs → src/archive/mod.rs


+ 0 - 0
cli/src/cmd/arg/download_limit.rs → src/cmd/arg/download_limit.rs


+ 0 - 0
cli/src/cmd/arg/host.rs → src/cmd/arg/host.rs


+ 0 - 0
cli/src/cmd/arg/mod.rs → src/cmd/arg/mod.rs


+ 0 - 0
cli/src/cmd/arg/owner.rs → src/cmd/arg/owner.rs


+ 0 - 0
cli/src/cmd/arg/password.rs → src/cmd/arg/password.rs


+ 0 - 0
cli/src/cmd/arg/url.rs → src/cmd/arg/url.rs


+ 0 - 0
cli/src/cmd/handler.rs → src/cmd/handler.rs


+ 0 - 0
cli/src/cmd/matcher/debug.rs → src/cmd/matcher/debug.rs


+ 0 - 0
cli/src/cmd/matcher/delete.rs → src/cmd/matcher/delete.rs


+ 0 - 0
cli/src/cmd/matcher/download.rs → src/cmd/matcher/download.rs


+ 0 - 0
cli/src/cmd/matcher/exists.rs → src/cmd/matcher/exists.rs


+ 0 - 0
cli/src/cmd/matcher/history.rs → src/cmd/matcher/history.rs


+ 0 - 0
cli/src/cmd/matcher/info.rs → src/cmd/matcher/info.rs


+ 0 - 0
cli/src/cmd/matcher/main.rs → src/cmd/matcher/main.rs


+ 0 - 0
cli/src/cmd/matcher/mod.rs → src/cmd/matcher/mod.rs


+ 0 - 0
cli/src/cmd/matcher/params.rs → src/cmd/matcher/params.rs


+ 0 - 0
cli/src/cmd/matcher/password.rs → src/cmd/matcher/password.rs


+ 0 - 0
cli/src/cmd/matcher/upload.rs → src/cmd/matcher/upload.rs


+ 0 - 0
cli/src/cmd/mod.rs → src/cmd/mod.rs


+ 0 - 0
cli/src/cmd/subcmd/debug.rs → src/cmd/subcmd/debug.rs


+ 0 - 0
cli/src/cmd/subcmd/delete.rs → src/cmd/subcmd/delete.rs


+ 0 - 0
cli/src/cmd/subcmd/download.rs → src/cmd/subcmd/download.rs


+ 0 - 0
cli/src/cmd/subcmd/exists.rs → src/cmd/subcmd/exists.rs


+ 0 - 0
cli/src/cmd/subcmd/history.rs → src/cmd/subcmd/history.rs


+ 0 - 0
cli/src/cmd/subcmd/info.rs → src/cmd/subcmd/info.rs


+ 0 - 0
cli/src/cmd/subcmd/mod.rs → src/cmd/subcmd/mod.rs


+ 0 - 0
cli/src/cmd/subcmd/params.rs → src/cmd/subcmd/params.rs


+ 0 - 0
cli/src/cmd/subcmd/password.rs → src/cmd/subcmd/password.rs


+ 0 - 0
cli/src/cmd/subcmd/upload.rs → src/cmd/subcmd/upload.rs


+ 0 - 0
cli/src/error.rs → src/error.rs


+ 0 - 0
cli/src/history.rs → src/history.rs


+ 0 - 0
cli/src/history_tool.rs → src/history_tool.rs


+ 0 - 0
cli/src/host.rs → src/host.rs


+ 0 - 0
cli/src/main.rs → src/main.rs


+ 0 - 0
cli/src/progress.rs → src/progress.rs


+ 0 - 0
cli/src/util.rs → src/util.rs