Commit
+4971 -0 +/-25 browse
1 | diff --git a/.cirrus.yml b/.cirrus.yml |
2 | new file mode 100644 |
3 | index 0000000..31dc9d0 |
4 | --- /dev/null |
5 | +++ b/.cirrus.yml |
6 | @@ -0,0 +1,32 @@ |
7 | + # Check that formatting is correct using nightly rust. |
8 | + fmt_task: |
9 | + container: |
10 | + image: rustlang/rust:nightly |
11 | + install_script: rustup component add rustfmt-preview |
12 | + check_script: cargo fmt -- --check |
13 | + |
14 | + # Run clippy. |
15 | + clippy_task: |
16 | + container: |
17 | + image: rustlang/rust:nightly |
18 | + cargo_cache: |
19 | + folder: $CARGO_HOME/registry |
20 | + fingerprint_script: cat Cargo.lock |
21 | + install_script: rustup component add clippy-preview |
22 | + check_script: cargo clippy |
23 | + before_cache_script: rm -rf $CARGO_HOME/registry/index |
24 | + |
25 | + # Build and test. |
26 | + test_task: |
27 | + matrix: |
28 | + - container: |
29 | + image: rust:latest |
30 | + - allow_failures: true |
31 | + container: |
32 | + image: rustlang/rust:nightly |
33 | + cargo_cache: |
34 | + folder: $CARGO_HOME/registry |
35 | + fingerprint_script: cat Cargo.lock |
36 | + build_script: cargo build |
37 | + test_script: cargo test |
38 | + before_cache_script: rm -rf $CARGO_HOME/registry/index |
39 | diff --git a/.dockerignore b/.dockerignore |
40 | new file mode 100644 |
41 | index 0000000..8d42d79 |
42 | --- /dev/null |
43 | +++ b/.dockerignore |
44 | @@ -0,0 +1,7 @@ |
45 | + # Ignore everything |
46 | + ** |
47 | + |
48 | + # Only include these files. |
49 | + !/src/** |
50 | + !/Cargo.toml |
51 | + !/Cargo.lock |
52 | diff --git a/.gitignore b/.gitignore |
53 | new file mode 100644 |
54 | index 0000000..9340db5 |
55 | --- /dev/null |
56 | +++ b/.gitignore |
57 | @@ -0,0 +1,5 @@ |
58 | + /target |
59 | + **/*.rs.bk |
60 | + /test.sh |
61 | + /cache/ |
62 | + .env |
63 | diff --git a/.license_template b/.license_template |
64 | new file mode 100644 |
65 | index 0000000..65079b2 |
66 | --- /dev/null |
67 | +++ b/.license_template |
68 | @@ -0,0 +1,19 @@ |
69 | + // Copyright (c) {\d+} Jason White |
70 | + // |
71 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
72 | + // of this software and associated documentation files (the "Software"), to deal |
73 | + // in the Software without restriction, including without limitation the rights |
74 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
75 | + // copies of the Software, and to permit persons to whom the Software is |
76 | + // furnished to do so, subject to the following conditions: |
77 | + // |
78 | + // The above copyright notice and this permission notice shall be included in |
79 | + // all copies or substantial portions of the Software. |
80 | + // |
81 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
82 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
83 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
84 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
85 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
86 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
87 | + // SOFTWARE. |
88 | diff --git a/Cargo.lock b/Cargo.lock |
89 | new file mode 100644 |
90 | index 0000000..5872c48 |
91 | --- /dev/null |
92 | +++ b/Cargo.lock |
93 | @@ -0,0 +1,1783 @@ |
94 | + # This file is automatically @generated by Cargo. |
95 | + # It is not intended for manual editing. |
96 | + [[package]] |
97 | + name = "aho-corasick" |
98 | + version = "0.6.10" |
99 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
100 | + dependencies = [ |
101 | + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
102 | + ] |
103 | + |
104 | + [[package]] |
105 | + name = "ansi_term" |
106 | + version = "0.11.0" |
107 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
108 | + dependencies = [ |
109 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
110 | + ] |
111 | + |
112 | + [[package]] |
113 | + name = "argon2rs" |
114 | + version = "0.2.5" |
115 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
116 | + dependencies = [ |
117 | + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", |
118 | + "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", |
119 | + ] |
120 | + |
121 | + [[package]] |
122 | + name = "arrayref" |
123 | + version = "0.3.5" |
124 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
125 | + |
126 | + [[package]] |
127 | + name = "arrayvec" |
128 | + version = "0.4.10" |
129 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
130 | + dependencies = [ |
131 | + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", |
132 | + ] |
133 | + |
134 | + [[package]] |
135 | + name = "atty" |
136 | + version = "0.2.11" |
137 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
138 | + dependencies = [ |
139 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
140 | + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", |
141 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
142 | + ] |
143 | + |
144 | + [[package]] |
145 | + name = "autocfg" |
146 | + version = "0.1.2" |
147 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
148 | + |
149 | + [[package]] |
150 | + name = "backtrace" |
151 | + version = "0.3.14" |
152 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
153 | + dependencies = [ |
154 | + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
155 | + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", |
156 | + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", |
157 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
158 | + "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", |
159 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
160 | + ] |
161 | + |
162 | + [[package]] |
163 | + name = "backtrace-sys" |
164 | + version = "0.1.28" |
165 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
166 | + dependencies = [ |
167 | + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", |
168 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
169 | + ] |
170 | + |
171 | + [[package]] |
172 | + name = "base64" |
173 | + version = "0.9.3" |
174 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
175 | + dependencies = [ |
176 | + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
177 | + "safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
178 | + ] |
179 | + |
180 | + [[package]] |
181 | + name = "bitflags" |
182 | + version = "1.0.4" |
183 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
184 | + |
185 | + [[package]] |
186 | + name = "blake2-rfc" |
187 | + version = "0.2.18" |
188 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
189 | + dependencies = [ |
190 | + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", |
191 | + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
192 | + ] |
193 | + |
194 | + [[package]] |
195 | + name = "block-buffer" |
196 | + version = "0.3.3" |
197 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
198 | + dependencies = [ |
199 | + "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", |
200 | + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
201 | + ] |
202 | + |
203 | + [[package]] |
204 | + name = "block-buffer" |
205 | + version = "0.7.0" |
206 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
207 | + dependencies = [ |
208 | + "block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
209 | + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
210 | + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
211 | + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", |
212 | + ] |
213 | + |
214 | + [[package]] |
215 | + name = "block-padding" |
216 | + version = "0.1.3" |
217 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
218 | + dependencies = [ |
219 | + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
220 | + ] |
221 | + |
222 | + [[package]] |
223 | + name = "byte-tools" |
224 | + version = "0.2.0" |
225 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
226 | + |
227 | + [[package]] |
228 | + name = "byte-tools" |
229 | + version = "0.3.1" |
230 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
231 | + |
232 | + [[package]] |
233 | + name = "byteorder" |
234 | + version = "1.3.1" |
235 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
236 | + |
237 | + [[package]] |
238 | + name = "bytes" |
239 | + version = "0.4.12" |
240 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
241 | + dependencies = [ |
242 | + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
243 | + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
244 | + ] |
245 | + |
246 | + [[package]] |
247 | + name = "cc" |
248 | + version = "1.0.31" |
249 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
250 | + |
251 | + [[package]] |
252 | + name = "cfg-if" |
253 | + version = "0.1.7" |
254 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
255 | + |
256 | + [[package]] |
257 | + name = "chacha" |
258 | + version = "0.3.0" |
259 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
260 | + dependencies = [ |
261 | + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
262 | + "keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", |
263 | + ] |
264 | + |
265 | + [[package]] |
266 | + name = "chrono" |
267 | + version = "0.4.6" |
268 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
269 | + dependencies = [ |
270 | + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", |
271 | + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", |
272 | + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
273 | + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", |
274 | + ] |
275 | + |
276 | + [[package]] |
277 | + name = "clap" |
278 | + version = "2.32.0" |
279 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
280 | + dependencies = [ |
281 | + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", |
282 | + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", |
283 | + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
284 | + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
285 | + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", |
286 | + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
287 | + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", |
288 | + ] |
289 | + |
290 | + [[package]] |
291 | + name = "cloudabi" |
292 | + version = "0.0.3" |
293 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
294 | + dependencies = [ |
295 | + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
296 | + ] |
297 | + |
298 | + [[package]] |
299 | + name = "constant_time_eq" |
300 | + version = "0.1.3" |
301 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
302 | + |
303 | + [[package]] |
304 | + name = "crossbeam-deque" |
305 | + version = "0.7.1" |
306 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
307 | + dependencies = [ |
308 | + "crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
309 | + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
310 | + ] |
311 | + |
312 | + [[package]] |
313 | + name = "crossbeam-epoch" |
314 | + version = "0.7.1" |
315 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
316 | + dependencies = [ |
317 | + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", |
318 | + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", |
319 | + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
320 | + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
321 | + "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", |
322 | + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
323 | + ] |
324 | + |
325 | + [[package]] |
326 | + name = "crossbeam-queue" |
327 | + version = "0.1.2" |
328 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
329 | + dependencies = [ |
330 | + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
331 | + ] |
332 | + |
333 | + [[package]] |
334 | + name = "crossbeam-utils" |
335 | + version = "0.6.5" |
336 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
337 | + dependencies = [ |
338 | + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", |
339 | + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
340 | + ] |
341 | + |
342 | + [[package]] |
343 | + name = "crypto-mac" |
344 | + version = "0.5.2" |
345 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
346 | + dependencies = [ |
347 | + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
348 | + "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", |
349 | + ] |
350 | + |
351 | + [[package]] |
352 | + name = "ct-logs" |
353 | + version = "0.4.0" |
354 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
355 | + dependencies = [ |
356 | + "sct 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
357 | + ] |
358 | + |
359 | + [[package]] |
360 | + name = "derive_more" |
361 | + version = "0.14.0" |
362 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
363 | + dependencies = [ |
364 | + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", |
365 | + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", |
366 | + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", |
367 | + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", |
368 | + ] |
369 | + |
370 | + [[package]] |
371 | + name = "digest" |
372 | + version = "0.7.6" |
373 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
374 | + dependencies = [ |
375 | + "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", |
376 | + ] |
377 | + |
378 | + [[package]] |
379 | + name = "digest" |
380 | + version = "0.8.0" |
381 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
382 | + dependencies = [ |
383 | + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", |
384 | + ] |
385 | + |
386 | + [[package]] |
387 | + name = "dirs" |
388 | + version = "1.0.5" |
389 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
390 | + dependencies = [ |
391 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
392 | + "redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
393 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
394 | + ] |
395 | + |
396 | + [[package]] |
397 | + name = "env_logger" |
398 | + version = "0.6.1" |
399 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
400 | + dependencies = [ |
401 | + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", |
402 | + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
403 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
404 | + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
405 | + "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
406 | + ] |
407 | + |
408 | + [[package]] |
409 | + name = "failure" |
410 | + version = "0.1.5" |
411 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
412 | + dependencies = [ |
413 | + "backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", |
414 | + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
415 | + ] |
416 | + |
417 | + [[package]] |
418 | + name = "failure_derive" |
419 | + version = "0.1.5" |
420 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
421 | + dependencies = [ |
422 | + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", |
423 | + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", |
424 | + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", |
425 | + "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", |
426 | + ] |
427 | + |
428 | + [[package]] |
429 | + name = "fake-simd" |
430 | + version = "0.1.2" |
431 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
432 | + |
433 | + [[package]] |
434 | + name = "fnv" |
435 | + version = "1.0.6" |
436 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
437 | + |
438 | + [[package]] |
439 | + name = "fuchsia-cprng" |
440 | + version = "0.1.1" |
441 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
442 | + |
443 | + [[package]] |
444 | + name = "fuchsia-zircon" |
445 | + version = "0.3.3" |
446 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
447 | + dependencies = [ |
448 | + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
449 | + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
450 | + ] |
451 | + |
452 | + [[package]] |
453 | + name = "fuchsia-zircon-sys" |
454 | + version = "0.3.3" |
455 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
456 | + |
457 | + [[package]] |
458 | + name = "futures" |
459 | + version = "0.1.25" |
460 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
461 | + |
462 | + [[package]] |
463 | + name = "futures-cpupool" |
464 | + version = "0.1.8" |
465 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
466 | + dependencies = [ |
467 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
468 | + "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", |
469 | + ] |
470 | + |
471 | + [[package]] |
472 | + name = "generic-array" |
473 | + version = "0.9.0" |
474 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
475 | + dependencies = [ |
476 | + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", |
477 | + ] |
478 | + |
479 | + [[package]] |
480 | + name = "generic-array" |
481 | + version = "0.12.0" |
482 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
483 | + dependencies = [ |
484 | + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", |
485 | + ] |
486 | + |
487 | + [[package]] |
488 | + name = "h2" |
489 | + version = "0.1.16" |
490 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
491 | + dependencies = [ |
492 | + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
493 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
494 | + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", |
495 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
496 | + "http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
497 | + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", |
498 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
499 | + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", |
500 | + "string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
501 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
502 | + ] |
503 | + |
504 | + [[package]] |
505 | + name = "heck" |
506 | + version = "0.3.1" |
507 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
508 | + dependencies = [ |
509 | + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", |
510 | + ] |
511 | + |
512 | + [[package]] |
513 | + name = "hex" |
514 | + version = "0.3.2" |
515 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
516 | + |
517 | + [[package]] |
518 | + name = "hmac" |
519 | + version = "0.5.0" |
520 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
521 | + dependencies = [ |
522 | + "crypto-mac 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", |
523 | + "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", |
524 | + ] |
525 | + |
526 | + [[package]] |
527 | + name = "http" |
528 | + version = "0.1.16" |
529 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
530 | + dependencies = [ |
531 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
532 | + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", |
533 | + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", |
534 | + ] |
535 | + |
536 | + [[package]] |
537 | + name = "httparse" |
538 | + version = "1.3.3" |
539 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
540 | + |
541 | + [[package]] |
542 | + name = "human-size" |
543 | + version = "0.4.0" |
544 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
545 | + |
546 | + [[package]] |
547 | + name = "humantime" |
548 | + version = "1.2.0" |
549 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
550 | + dependencies = [ |
551 | + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", |
552 | + ] |
553 | + |
554 | + [[package]] |
555 | + name = "hyper" |
556 | + version = "0.12.25" |
557 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
558 | + dependencies = [ |
559 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
560 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
561 | + "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", |
562 | + "h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
563 | + "http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
564 | + "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
565 | + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
566 | + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", |
567 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
568 | + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", |
569 | + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", |
570 | + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", |
571 | + "tokio 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
572 | + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
573 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
574 | + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", |
575 | + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
576 | + "tokio-threadpool 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
577 | + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", |
578 | + "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", |
579 | + ] |
580 | + |
581 | + [[package]] |
582 | + name = "hyper-rustls" |
583 | + version = "0.14.0" |
584 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
585 | + dependencies = [ |
586 | + "ct-logs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
587 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
588 | + "http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
589 | + "hyper 0.12.25 (registry+https://github.com/rust-lang/crates.io-index)", |
590 | + "rustls 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", |
591 | + "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", |
592 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
593 | + "tokio-rustls 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", |
594 | + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
595 | + "webpki 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", |
596 | + "webpki-roots 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", |
597 | + ] |
598 | + |
599 | + [[package]] |
600 | + name = "idna" |
601 | + version = "0.1.5" |
602 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
603 | + dependencies = [ |
604 | + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", |
605 | + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", |
606 | + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", |
607 | + ] |
608 | + |
609 | + [[package]] |
610 | + name = "indexmap" |
611 | + version = "1.0.2" |
612 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
613 | + |
614 | + [[package]] |
615 | + name = "iovec" |
616 | + version = "0.1.2" |
617 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
618 | + dependencies = [ |
619 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
620 | + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
621 | + ] |
622 | + |
623 | + [[package]] |
624 | + name = "itoa" |
625 | + version = "0.4.3" |
626 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
627 | + |
628 | + [[package]] |
629 | + name = "kernel32-sys" |
630 | + version = "0.2.2" |
631 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
632 | + dependencies = [ |
633 | + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
634 | + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
635 | + ] |
636 | + |
637 | + [[package]] |
638 | + name = "keystream" |
639 | + version = "1.0.0" |
640 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
641 | + |
642 | + [[package]] |
643 | + name = "lazy_static" |
644 | + version = "1.3.0" |
645 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
646 | + |
647 | + [[package]] |
648 | + name = "lazycell" |
649 | + version = "1.2.1" |
650 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
651 | + |
652 | + [[package]] |
653 | + name = "libc" |
654 | + version = "0.2.50" |
655 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
656 | + |
657 | + [[package]] |
658 | + name = "linked-hash-map" |
659 | + version = "0.5.1" |
660 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
661 | + dependencies = [ |
662 | + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
663 | + "serde_test 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
664 | + ] |
665 | + |
666 | + [[package]] |
667 | + name = "lock_api" |
668 | + version = "0.1.5" |
669 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
670 | + dependencies = [ |
671 | + "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
672 | + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
673 | + ] |
674 | + |
675 | + [[package]] |
676 | + name = "log" |
677 | + version = "0.4.6" |
678 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
679 | + dependencies = [ |
680 | + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", |
681 | + ] |
682 | + |
683 | + [[package]] |
684 | + name = "matches" |
685 | + version = "0.1.8" |
686 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
687 | + |
688 | + [[package]] |
689 | + name = "md5" |
690 | + version = "0.3.8" |
691 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
692 | + |
693 | + [[package]] |
694 | + name = "memchr" |
695 | + version = "2.2.0" |
696 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
697 | + |
698 | + [[package]] |
699 | + name = "memoffset" |
700 | + version = "0.2.1" |
701 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
702 | + |
703 | + [[package]] |
704 | + name = "mio" |
705 | + version = "0.6.16" |
706 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
707 | + dependencies = [ |
708 | + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
709 | + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
710 | + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
711 | + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", |
712 | + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", |
713 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
714 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
715 | + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", |
716 | + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", |
717 | + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", |
718 | + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
719 | + ] |
720 | + |
721 | + [[package]] |
722 | + name = "mio-uds" |
723 | + version = "0.6.7" |
724 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
725 | + dependencies = [ |
726 | + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
727 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
728 | + "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", |
729 | + ] |
730 | + |
731 | + [[package]] |
732 | + name = "miow" |
733 | + version = "0.2.1" |
734 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
735 | + dependencies = [ |
736 | + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", |
737 | + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", |
738 | + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
739 | + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", |
740 | + ] |
741 | + |
742 | + [[package]] |
743 | + name = "net2" |
744 | + version = "0.2.33" |
745 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
746 | + dependencies = [ |
747 | + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", |
748 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
749 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
750 | + ] |
751 | + |
752 | + [[package]] |
753 | + name = "nodrop" |
754 | + version = "0.1.13" |
755 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
756 | + |
757 | + [[package]] |
758 | + name = "num-integer" |
759 | + version = "0.1.39" |
760 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
761 | + dependencies = [ |
762 | + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", |
763 | + ] |
764 | + |
765 | + [[package]] |
766 | + name = "num-traits" |
767 | + version = "0.2.6" |
768 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
769 | + |
770 | + [[package]] |
771 | + name = "num_cpus" |
772 | + version = "1.10.0" |
773 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
774 | + dependencies = [ |
775 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
776 | + ] |
777 | + |
778 | + [[package]] |
779 | + name = "opaque-debug" |
780 | + version = "0.2.2" |
781 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
782 | + |
783 | + [[package]] |
784 | + name = "owning_ref" |
785 | + version = "0.4.0" |
786 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
787 | + dependencies = [ |
788 | + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
789 | + ] |
790 | + |
791 | + [[package]] |
792 | + name = "parking_lot" |
793 | + version = "0.7.1" |
794 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
795 | + dependencies = [ |
796 | + "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
797 | + "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
798 | + ] |
799 | + |
800 | + [[package]] |
801 | + name = "parking_lot_core" |
802 | + version = "0.4.0" |
803 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
804 | + dependencies = [ |
805 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
806 | + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
807 | + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", |
808 | + "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", |
809 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
810 | + ] |
811 | + |
812 | + [[package]] |
813 | + name = "percent-encoding" |
814 | + version = "1.0.1" |
815 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
816 | + |
817 | + [[package]] |
818 | + name = "pretty_env_logger" |
819 | + version = "0.3.0" |
820 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
821 | + dependencies = [ |
822 | + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
823 | + "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", |
824 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
825 | + ] |
826 | + |
827 | + [[package]] |
828 | + name = "proc-macro2" |
829 | + version = "0.4.27" |
830 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
831 | + dependencies = [ |
832 | + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
833 | + ] |
834 | + |
835 | + [[package]] |
836 | + name = "quick-error" |
837 | + version = "1.2.2" |
838 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
839 | + |
840 | + [[package]] |
841 | + name = "quote" |
842 | + version = "0.6.11" |
843 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
844 | + dependencies = [ |
845 | + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", |
846 | + ] |
847 | + |
848 | + [[package]] |
849 | + name = "rand" |
850 | + version = "0.6.5" |
851 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
852 | + dependencies = [ |
853 | + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
854 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
855 | + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
856 | + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
857 | + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
858 | + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
859 | + "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
860 | + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
861 | + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
862 | + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
863 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
864 | + ] |
865 | + |
866 | + [[package]] |
867 | + name = "rand_chacha" |
868 | + version = "0.1.1" |
869 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
870 | + dependencies = [ |
871 | + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
872 | + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
873 | + ] |
874 | + |
875 | + [[package]] |
876 | + name = "rand_core" |
877 | + version = "0.3.1" |
878 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
879 | + dependencies = [ |
880 | + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
881 | + ] |
882 | + |
883 | + [[package]] |
884 | + name = "rand_core" |
885 | + version = "0.4.0" |
886 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
887 | + |
888 | + [[package]] |
889 | + name = "rand_hc" |
890 | + version = "0.1.0" |
891 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
892 | + dependencies = [ |
893 | + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
894 | + ] |
895 | + |
896 | + [[package]] |
897 | + name = "rand_isaac" |
898 | + version = "0.1.1" |
899 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
900 | + dependencies = [ |
901 | + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
902 | + ] |
903 | + |
904 | + [[package]] |
905 | + name = "rand_jitter" |
906 | + version = "0.1.3" |
907 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
908 | + dependencies = [ |
909 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
910 | + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
911 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
912 | + ] |
913 | + |
914 | + [[package]] |
915 | + name = "rand_os" |
916 | + version = "0.1.3" |
917 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
918 | + dependencies = [ |
919 | + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", |
920 | + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
921 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
922 | + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
923 | + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
924 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
925 | + ] |
926 | + |
927 | + [[package]] |
928 | + name = "rand_pcg" |
929 | + version = "0.1.2" |
930 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
931 | + dependencies = [ |
932 | + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
933 | + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
934 | + ] |
935 | + |
936 | + [[package]] |
937 | + name = "rand_xorshift" |
938 | + version = "0.1.1" |
939 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
940 | + dependencies = [ |
941 | + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
942 | + ] |
943 | + |
944 | + [[package]] |
945 | + name = "rdrand" |
946 | + version = "0.4.0" |
947 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
948 | + dependencies = [ |
949 | + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
950 | + ] |
951 | + |
952 | + [[package]] |
953 | + name = "redox_syscall" |
954 | + version = "0.1.51" |
955 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
956 | + |
957 | + [[package]] |
958 | + name = "redox_termios" |
959 | + version = "0.1.1" |
960 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
961 | + dependencies = [ |
962 | + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", |
963 | + ] |
964 | + |
965 | + [[package]] |
966 | + name = "redox_users" |
967 | + version = "0.3.0" |
968 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
969 | + dependencies = [ |
970 | + "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", |
971 | + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
972 | + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
973 | + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", |
974 | + ] |
975 | + |
976 | + [[package]] |
977 | + name = "regex" |
978 | + version = "1.1.2" |
979 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
980 | + dependencies = [ |
981 | + "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", |
982 | + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
983 | + "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
984 | + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
985 | + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", |
986 | + ] |
987 | + |
988 | + [[package]] |
989 | + name = "regex-syntax" |
990 | + version = "0.6.5" |
991 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
992 | + dependencies = [ |
993 | + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
994 | + ] |
995 | + |
996 | + [[package]] |
997 | + name = "ring" |
998 | + version = "0.13.5" |
999 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1000 | + dependencies = [ |
1001 | + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", |
1002 | + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1003 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
1004 | + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1005 | + ] |
1006 | + |
1007 | + [[package]] |
1008 | + name = "rudolfs" |
1009 | + version = "0.1.0" |
1010 | + dependencies = [ |
1011 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1012 | + "chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1013 | + "derive_more 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1014 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1015 | + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1016 | + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1017 | + "http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1018 | + "human-size 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1019 | + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1020 | + "hyper 0.12.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1021 | + "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1022 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1023 | + "pretty_env_logger 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1024 | + "rusoto_core 0.36.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1025 | + "rusoto_s3 0.36.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1026 | + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
1027 | + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", |
1028 | + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1029 | + "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", |
1030 | + "tokio 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1031 | + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1032 | + "uuid 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1033 | + ] |
1034 | + |
1035 | + [[package]] |
1036 | + name = "rusoto_core" |
1037 | + version = "0.36.0" |
1038 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1039 | + dependencies = [ |
1040 | + "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1041 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1042 | + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1043 | + "hmac 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1044 | + "hyper 0.12.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1045 | + "hyper-rustls 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1046 | + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1047 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1048 | + "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", |
1049 | + "rusoto_credential 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1050 | + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1051 | + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
1052 | + "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1053 | + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", |
1054 | + "tokio 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1055 | + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", |
1056 | + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1057 | + "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1058 | + ] |
1059 | + |
1060 | + [[package]] |
1061 | + name = "rusoto_credential" |
1062 | + version = "0.15.0" |
1063 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1064 | + dependencies = [ |
1065 | + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1066 | + "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1067 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1068 | + "hyper 0.12.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1069 | + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1070 | + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
1071 | + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
1072 | + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", |
1073 | + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", |
1074 | + ] |
1075 | + |
1076 | + [[package]] |
1077 | + name = "rusoto_s3" |
1078 | + version = "0.36.0" |
1079 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1080 | + dependencies = [ |
1081 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1082 | + "rusoto_core 0.36.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1083 | + "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1084 | + ] |
1085 | + |
1086 | + [[package]] |
1087 | + name = "rustc-demangle" |
1088 | + version = "0.1.13" |
1089 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1090 | + |
1091 | + [[package]] |
1092 | + name = "rustc_version" |
1093 | + version = "0.2.3" |
1094 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1095 | + dependencies = [ |
1096 | + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1097 | + ] |
1098 | + |
1099 | + [[package]] |
1100 | + name = "rustls" |
1101 | + version = "0.13.1" |
1102 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1103 | + dependencies = [ |
1104 | + "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1105 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1106 | + "ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1107 | + "sct 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1108 | + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1109 | + "webpki 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1110 | + ] |
1111 | + |
1112 | + [[package]] |
1113 | + name = "ryu" |
1114 | + version = "0.2.7" |
1115 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1116 | + |
1117 | + [[package]] |
1118 | + name = "safemem" |
1119 | + version = "0.3.0" |
1120 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1121 | + |
1122 | + [[package]] |
1123 | + name = "scoped-tls" |
1124 | + version = "0.1.2" |
1125 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1126 | + |
1127 | + [[package]] |
1128 | + name = "scoped_threadpool" |
1129 | + version = "0.1.9" |
1130 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1131 | + |
1132 | + [[package]] |
1133 | + name = "scopeguard" |
1134 | + version = "0.3.3" |
1135 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1136 | + |
1137 | + [[package]] |
1138 | + name = "sct" |
1139 | + version = "0.4.0" |
1140 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1141 | + dependencies = [ |
1142 | + "ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1143 | + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1144 | + ] |
1145 | + |
1146 | + [[package]] |
1147 | + name = "semver" |
1148 | + version = "0.9.0" |
1149 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1150 | + dependencies = [ |
1151 | + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1152 | + ] |
1153 | + |
1154 | + [[package]] |
1155 | + name = "semver-parser" |
1156 | + version = "0.7.0" |
1157 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1158 | + |
1159 | + [[package]] |
1160 | + name = "serde" |
1161 | + version = "1.0.89" |
1162 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1163 | + dependencies = [ |
1164 | + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
1165 | + ] |
1166 | + |
1167 | + [[package]] |
1168 | + name = "serde_derive" |
1169 | + version = "1.0.89" |
1170 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1171 | + dependencies = [ |
1172 | + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", |
1173 | + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", |
1174 | + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", |
1175 | + ] |
1176 | + |
1177 | + [[package]] |
1178 | + name = "serde_json" |
1179 | + version = "1.0.39" |
1180 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1181 | + dependencies = [ |
1182 | + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1183 | + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", |
1184 | + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
1185 | + ] |
1186 | + |
1187 | + [[package]] |
1188 | + name = "serde_test" |
1189 | + version = "1.0.89" |
1190 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1191 | + dependencies = [ |
1192 | + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", |
1193 | + ] |
1194 | + |
1195 | + [[package]] |
1196 | + name = "sha2" |
1197 | + version = "0.7.1" |
1198 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1199 | + dependencies = [ |
1200 | + "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1201 | + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1202 | + "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1203 | + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1204 | + ] |
1205 | + |
1206 | + [[package]] |
1207 | + name = "sha2" |
1208 | + version = "0.8.0" |
1209 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1210 | + dependencies = [ |
1211 | + "block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1212 | + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1213 | + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1214 | + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1215 | + ] |
1216 | + |
1217 | + [[package]] |
1218 | + name = "slab" |
1219 | + version = "0.4.2" |
1220 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1221 | + |
1222 | + [[package]] |
1223 | + name = "smallvec" |
1224 | + version = "0.6.9" |
1225 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1226 | + |
1227 | + [[package]] |
1228 | + name = "stable_deref_trait" |
1229 | + version = "1.1.1" |
1230 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1231 | + |
1232 | + [[package]] |
1233 | + name = "string" |
1234 | + version = "0.1.3" |
1235 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1236 | + |
1237 | + [[package]] |
1238 | + name = "strsim" |
1239 | + version = "0.7.0" |
1240 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1241 | + |
1242 | + [[package]] |
1243 | + name = "structopt" |
1244 | + version = "0.2.15" |
1245 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1246 | + dependencies = [ |
1247 | + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1248 | + "structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", |
1249 | + ] |
1250 | + |
1251 | + [[package]] |
1252 | + name = "structopt-derive" |
1253 | + version = "0.2.15" |
1254 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1255 | + dependencies = [ |
1256 | + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1257 | + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", |
1258 | + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", |
1259 | + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", |
1260 | + ] |
1261 | + |
1262 | + [[package]] |
1263 | + name = "syn" |
1264 | + version = "0.15.29" |
1265 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1266 | + dependencies = [ |
1267 | + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", |
1268 | + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", |
1269 | + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1270 | + ] |
1271 | + |
1272 | + [[package]] |
1273 | + name = "synstructure" |
1274 | + version = "0.10.1" |
1275 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1276 | + dependencies = [ |
1277 | + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", |
1278 | + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", |
1279 | + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", |
1280 | + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1281 | + ] |
1282 | + |
1283 | + [[package]] |
1284 | + name = "termcolor" |
1285 | + version = "1.0.4" |
1286 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1287 | + dependencies = [ |
1288 | + "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1289 | + ] |
1290 | + |
1291 | + [[package]] |
1292 | + name = "termion" |
1293 | + version = "1.5.1" |
1294 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1295 | + dependencies = [ |
1296 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
1297 | + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", |
1298 | + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1299 | + ] |
1300 | + |
1301 | + [[package]] |
1302 | + name = "textwrap" |
1303 | + version = "0.10.0" |
1304 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1305 | + dependencies = [ |
1306 | + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1307 | + ] |
1308 | + |
1309 | + [[package]] |
1310 | + name = "thread_local" |
1311 | + version = "0.3.6" |
1312 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1313 | + dependencies = [ |
1314 | + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1315 | + ] |
1316 | + |
1317 | + [[package]] |
1318 | + name = "time" |
1319 | + version = "0.1.42" |
1320 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1321 | + dependencies = [ |
1322 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
1323 | + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", |
1324 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1325 | + ] |
1326 | + |
1327 | + [[package]] |
1328 | + name = "tokio" |
1329 | + version = "0.1.16" |
1330 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1331 | + dependencies = [ |
1332 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1333 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1334 | + "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1335 | + "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1336 | + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1337 | + "tokio-current-thread 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1338 | + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1339 | + "tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1340 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1341 | + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", |
1342 | + "tokio-sync 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1343 | + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1344 | + "tokio-threadpool 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1345 | + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", |
1346 | + "tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1347 | + "tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1348 | + ] |
1349 | + |
1350 | + [[package]] |
1351 | + name = "tokio-codec" |
1352 | + version = "0.1.1" |
1353 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1354 | + dependencies = [ |
1355 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1356 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1357 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1358 | + ] |
1359 | + |
1360 | + [[package]] |
1361 | + name = "tokio-core" |
1362 | + version = "0.1.17" |
1363 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1364 | + dependencies = [ |
1365 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1366 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1367 | + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1368 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1369 | + "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1370 | + "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1371 | + "tokio 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1372 | + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1373 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1374 | + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", |
1375 | + "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", |
1376 | + ] |
1377 | + |
1378 | + [[package]] |
1379 | + name = "tokio-current-thread" |
1380 | + version = "0.1.5" |
1381 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1382 | + dependencies = [ |
1383 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1384 | + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1385 | + ] |
1386 | + |
1387 | + [[package]] |
1388 | + name = "tokio-executor" |
1389 | + version = "0.1.6" |
1390 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1391 | + dependencies = [ |
1392 | + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1393 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1394 | + ] |
1395 | + |
1396 | + [[package]] |
1397 | + name = "tokio-fs" |
1398 | + version = "0.1.6" |
1399 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1400 | + dependencies = [ |
1401 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1402 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1403 | + "tokio-threadpool 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1404 | + ] |
1405 | + |
1406 | + [[package]] |
1407 | + name = "tokio-io" |
1408 | + version = "0.1.12" |
1409 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1410 | + dependencies = [ |
1411 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1412 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1413 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1414 | + ] |
1415 | + |
1416 | + [[package]] |
1417 | + name = "tokio-reactor" |
1418 | + version = "0.1.9" |
1419 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1420 | + dependencies = [ |
1421 | + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1422 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1423 | + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1424 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1425 | + "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1426 | + "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1427 | + "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1428 | + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1429 | + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1430 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1431 | + "tokio-sync 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1432 | + ] |
1433 | + |
1434 | + [[package]] |
1435 | + name = "tokio-rustls" |
1436 | + version = "0.7.2" |
1437 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1438 | + dependencies = [ |
1439 | + "rustls 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1440 | + "tokio 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1441 | + "webpki 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1442 | + ] |
1443 | + |
1444 | + [[package]] |
1445 | + name = "tokio-sync" |
1446 | + version = "0.1.3" |
1447 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1448 | + dependencies = [ |
1449 | + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1450 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1451 | + ] |
1452 | + |
1453 | + [[package]] |
1454 | + name = "tokio-tcp" |
1455 | + version = "0.1.3" |
1456 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1457 | + dependencies = [ |
1458 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1459 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1460 | + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1461 | + "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1462 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1463 | + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", |
1464 | + ] |
1465 | + |
1466 | + [[package]] |
1467 | + name = "tokio-threadpool" |
1468 | + version = "0.1.12" |
1469 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1470 | + dependencies = [ |
1471 | + "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1472 | + "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1473 | + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1474 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1475 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1476 | + "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1477 | + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1478 | + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1479 | + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1480 | + ] |
1481 | + |
1482 | + [[package]] |
1483 | + name = "tokio-timer" |
1484 | + version = "0.2.10" |
1485 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1486 | + dependencies = [ |
1487 | + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1488 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1489 | + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1490 | + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1491 | + ] |
1492 | + |
1493 | + [[package]] |
1494 | + name = "tokio-udp" |
1495 | + version = "0.1.3" |
1496 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1497 | + dependencies = [ |
1498 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1499 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1500 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1501 | + "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1502 | + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1503 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1504 | + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", |
1505 | + ] |
1506 | + |
1507 | + [[package]] |
1508 | + name = "tokio-uds" |
1509 | + version = "0.2.5" |
1510 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1511 | + dependencies = [ |
1512 | + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1513 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1514 | + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1515 | + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", |
1516 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1517 | + "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", |
1518 | + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", |
1519 | + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1520 | + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", |
1521 | + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", |
1522 | + ] |
1523 | + |
1524 | + [[package]] |
1525 | + name = "try-lock" |
1526 | + version = "0.2.2" |
1527 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1528 | + |
1529 | + [[package]] |
1530 | + name = "typenum" |
1531 | + version = "1.10.0" |
1532 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1533 | + |
1534 | + [[package]] |
1535 | + name = "ucd-util" |
1536 | + version = "0.1.3" |
1537 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1538 | + |
1539 | + [[package]] |
1540 | + name = "unicode-bidi" |
1541 | + version = "0.3.4" |
1542 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1543 | + dependencies = [ |
1544 | + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", |
1545 | + ] |
1546 | + |
1547 | + [[package]] |
1548 | + name = "unicode-normalization" |
1549 | + version = "0.1.8" |
1550 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1551 | + dependencies = [ |
1552 | + "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", |
1553 | + ] |
1554 | + |
1555 | + [[package]] |
1556 | + name = "unicode-segmentation" |
1557 | + version = "1.2.1" |
1558 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1559 | + |
1560 | + [[package]] |
1561 | + name = "unicode-width" |
1562 | + version = "0.1.5" |
1563 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1564 | + |
1565 | + [[package]] |
1566 | + name = "unicode-xid" |
1567 | + version = "0.1.0" |
1568 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1569 | + |
1570 | + [[package]] |
1571 | + name = "untrusted" |
1572 | + version = "0.6.2" |
1573 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1574 | + |
1575 | + [[package]] |
1576 | + name = "url" |
1577 | + version = "1.7.2" |
1578 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1579 | + dependencies = [ |
1580 | + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1581 | + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", |
1582 | + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1583 | + ] |
1584 | + |
1585 | + [[package]] |
1586 | + name = "utf8-ranges" |
1587 | + version = "1.0.2" |
1588 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1589 | + |
1590 | + [[package]] |
1591 | + name = "uuid" |
1592 | + version = "0.7.2" |
1593 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1594 | + dependencies = [ |
1595 | + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1596 | + ] |
1597 | + |
1598 | + [[package]] |
1599 | + name = "vec_map" |
1600 | + version = "0.8.1" |
1601 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1602 | + |
1603 | + [[package]] |
1604 | + name = "want" |
1605 | + version = "0.0.6" |
1606 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1607 | + dependencies = [ |
1608 | + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", |
1609 | + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1610 | + "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1611 | + ] |
1612 | + |
1613 | + [[package]] |
1614 | + name = "webpki" |
1615 | + version = "0.18.1" |
1616 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1617 | + dependencies = [ |
1618 | + "ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1619 | + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1620 | + ] |
1621 | + |
1622 | + [[package]] |
1623 | + name = "webpki-roots" |
1624 | + version = "0.15.0" |
1625 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1626 | + dependencies = [ |
1627 | + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1628 | + "webpki 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1629 | + ] |
1630 | + |
1631 | + [[package]] |
1632 | + name = "winapi" |
1633 | + version = "0.2.8" |
1634 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1635 | + |
1636 | + [[package]] |
1637 | + name = "winapi" |
1638 | + version = "0.3.6" |
1639 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1640 | + dependencies = [ |
1641 | + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1642 | + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1643 | + ] |
1644 | + |
1645 | + [[package]] |
1646 | + name = "winapi-build" |
1647 | + version = "0.1.1" |
1648 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1649 | + |
1650 | + [[package]] |
1651 | + name = "winapi-i686-pc-windows-gnu" |
1652 | + version = "0.4.0" |
1653 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1654 | + |
1655 | + [[package]] |
1656 | + name = "winapi-util" |
1657 | + version = "0.1.2" |
1658 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1659 | + dependencies = [ |
1660 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1661 | + ] |
1662 | + |
1663 | + [[package]] |
1664 | + name = "winapi-x86_64-pc-windows-gnu" |
1665 | + version = "0.4.0" |
1666 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1667 | + |
1668 | + [[package]] |
1669 | + name = "wincolor" |
1670 | + version = "1.0.1" |
1671 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1672 | + dependencies = [ |
1673 | + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1674 | + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1675 | + ] |
1676 | + |
1677 | + [[package]] |
1678 | + name = "ws2_32-sys" |
1679 | + version = "0.2.1" |
1680 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1681 | + dependencies = [ |
1682 | + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |
1683 | + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1684 | + ] |
1685 | + |
1686 | + [[package]] |
1687 | + name = "xml-rs" |
1688 | + version = "0.7.0" |
1689 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
1690 | + dependencies = [ |
1691 | + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
1692 | + ] |
1693 | + |
1694 | + [metadata] |
1695 | + "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" |
1696 | + "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" |
1697 | + "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" |
1698 | + "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" |
1699 | + "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" |
1700 | + "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" |
1701 | + "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" |
1702 | + "checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" |
1703 | + "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" |
1704 | + "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" |
1705 | + "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" |
1706 | + "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" |
1707 | + "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" |
1708 | + "checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d" |
1709 | + "checksum block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d75255892aeb580d3c566f213a2b6fdc1c66667839f45719ee1d30ebf2aea591" |
1710 | + "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" |
1711 | + "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" |
1712 | + "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" |
1713 | + "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" |
1714 | + "checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" |
1715 | + "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" |
1716 | + "checksum chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" |
1717 | + "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" |
1718 | + "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" |
1719 | + "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" |
1720 | + "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" |
1721 | + "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" |
1722 | + "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" |
1723 | + "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" |
1724 | + "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" |
1725 | + "checksum crypto-mac 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0999b4ff4d3446d4ddb19a63e9e00c1876e75cd7000d20e57a693b4b3f08d958" |
1726 | + "checksum ct-logs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "95a4bf5107667e12bf6ce31a3a5066d67acc88942b6742117a41198734aaccaa" |
1727 | + "checksum derive_more 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fbe9f11be34f800b3ecaaed0ec9ec2e015d1d0ba0c8644c1310f73d6e8994615" |
1728 | + "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" |
1729 | + "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" |
1730 | + "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" |
1731 | + "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" |
1732 | + "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" |
1733 | + "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" |
1734 | + "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" |
1735 | + "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" |
1736 | + "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" |
1737 | + "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" |
1738 | + "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" |
1739 | + "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" |
1740 | + "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" |
1741 | + "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" |
1742 | + "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" |
1743 | + "checksum h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e" |
1744 | + "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" |
1745 | + "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" |
1746 | + "checksum hmac 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44f3bdb08579d99d7dc761c0e266f13b5f2ab8c8c703b9fc9ef333cd8f48f55e" |
1747 | + "checksum http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "fe67e3678f2827030e89cc4b9e7ecd16d52f132c0b940ab5005f88e821500f6a" |
1748 | + "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" |
1749 | + "checksum human-size 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bec6e801ef7367625bd94ad7e2965e6027189f3e9deef422388d993af2814a0" |
1750 | + "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" |
1751 | + "checksum hyper 0.12.25 (registry+https://github.com/rust-lang/crates.io-index)" = "7d5b6658b016965ae301fa995306db965c93677880ea70765a84235a96eae896" |
1752 | + "checksum hyper-rustls 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "68f2aa6b1681795bf4da8063f718cd23145aa0c9a5143d9787b345aa60d38ee4" |
1753 | + "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" |
1754 | + "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" |
1755 | + "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" |
1756 | + "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" |
1757 | + "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" |
1758 | + "checksum keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" |
1759 | + "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" |
1760 | + "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" |
1761 | + "checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" |
1762 | + "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" |
1763 | + "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" |
1764 | + "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" |
1765 | + "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" |
1766 | + "checksum md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" |
1767 | + "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" |
1768 | + "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" |
1769 | + "checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" |
1770 | + "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" |
1771 | + "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" |
1772 | + "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" |
1773 | + "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" |
1774 | + "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" |
1775 | + "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" |
1776 | + "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" |
1777 | + "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" |
1778 | + "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" |
1779 | + "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" |
1780 | + "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" |
1781 | + "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" |
1782 | + "checksum pretty_env_logger 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8b3f4e0475def7d9c2e5de8e5a1306949849761e107b360d03e98eafaffd61" |
1783 | + "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" |
1784 | + "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" |
1785 | + "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" |
1786 | + "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" |
1787 | + "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" |
1788 | + "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" |
1789 | + "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" |
1790 | + "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" |
1791 | + "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" |
1792 | + "checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" |
1793 | + "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" |
1794 | + "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" |
1795 | + "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" |
1796 | + "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" |
1797 | + "checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" |
1798 | + "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" |
1799 | + "checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" |
1800 | + "checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" |
1801 | + "checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" |
1802 | + "checksum ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2c4db68a2e35f3497146b7e4563df7d4773a2433230c5e4b448328e31740458a" |
1803 | + "checksum rusoto_core 0.36.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18a699355ef3189e3bbf34b64ff5a31f06456b689b09d05cdb4a901dcf4406a8" |
1804 | + "checksum rusoto_credential 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc8dd0f0a7e8b62f31aa23fa12fa0a7ac0e1eb52f6f4d4279d8a2ae51d8f099" |
1805 | + "checksum rusoto_s3 0.36.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1c7ed5248f4d330412be31522ef242de61a55d4c80f298dc7377121d52610ddb" |
1806 | + "checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" |
1807 | + "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" |
1808 | + "checksum rustls 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "942b71057b31981152970d57399c25f72e27a6ee0d207a669d8304cabf44705b" |
1809 | + "checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" |
1810 | + "checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" |
1811 | + "checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" |
1812 | + "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" |
1813 | + "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" |
1814 | + "checksum sct 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb8f61f9e6eadd062a71c380043d28036304a4706b3c4dd001ff3387ed00745a" |
1815 | + "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" |
1816 | + "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" |
1817 | + "checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" |
1818 | + "checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" |
1819 | + "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" |
1820 | + "checksum serde_test 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "70807e147558b5253cb70f55d343db1d07204d773087c96d0f35fced295dba82" |
1821 | + "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" |
1822 | + "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" |
1823 | + "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" |
1824 | + "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" |
1825 | + "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" |
1826 | + "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" |
1827 | + "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" |
1828 | + "checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1" |
1829 | + "checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6" |
1830 | + "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" |
1831 | + "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" |
1832 | + "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" |
1833 | + "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" |
1834 | + "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" |
1835 | + "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" |
1836 | + "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" |
1837 | + "checksum tokio 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "fcaabb3cec70485d0df6e9454fe514393ad1c4070dee8915f11041e95630b230" |
1838 | + "checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" |
1839 | + "checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" |
1840 | + "checksum tokio-current-thread 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c756b04680eea21902a46fca4e9f410a2332c04995af590e07ff262e2193a9a3" |
1841 | + "checksum tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30c6dbf2d1ad1de300b393910e8a3aa272b724a400b6531da03eed99e329fbf0" |
1842 | + "checksum tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" |
1843 | + "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" |
1844 | + "checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" |
1845 | + "checksum tokio-rustls 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "208d62fa3e015426e3c64039d9d20adf054a3c9b4d9445560f1c41c75bef3eab" |
1846 | + "checksum tokio-sync 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1bf2b9dac2a0509b5cfd1df5aa25eafacb616a42a491a13604d6bbeab4486363" |
1847 | + "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" |
1848 | + "checksum tokio-threadpool 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "742e511f6ce2298aeb86fc9ea0d8df81c2388c6ebae3dc8a7316e8c9df0df801" |
1849 | + "checksum tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" |
1850 | + "checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" |
1851 | + "checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" |
1852 | + "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" |
1853 | + "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" |
1854 | + "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" |
1855 | + "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" |
1856 | + "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" |
1857 | + "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" |
1858 | + "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" |
1859 | + "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" |
1860 | + "checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" |
1861 | + "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" |
1862 | + "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" |
1863 | + "checksum uuid 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0238db0c5b605dd1cf51de0f21766f97fba2645897024461d6a00c036819a768" |
1864 | + "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" |
1865 | + "checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" |
1866 | + "checksum webpki 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)" = "17d7967316d8411ca3b01821ee6c332bde138ba4363becdb492f12e514daa17f" |
1867 | + "checksum webpki-roots 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85d1f408918fd590908a70d36b7ac388db2edc221470333e4d6e5b598e44cabf" |
1868 | + "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" |
1869 | + "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" |
1870 | + "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" |
1871 | + "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" |
1872 | + "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" |
1873 | + "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" |
1874 | + "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" |
1875 | + "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" |
1876 | + "checksum xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" |
1877 | diff --git a/Cargo.toml b/Cargo.toml |
1878 | new file mode 100644 |
1879 | index 0000000..aec1174 |
1880 | --- /dev/null |
1881 | +++ b/Cargo.toml |
1882 | @@ -0,0 +1,37 @@ |
1883 | + [package] |
1884 | + name = "rudolfs" |
1885 | + version = "0.1.0" |
1886 | + authors = ["Jason White"] |
1887 | + edition = "2018" |
1888 | + |
1889 | + [dependencies] |
1890 | + bytes = "0.4" |
1891 | + chacha = "0.3" |
1892 | + derive_more = "0.14" |
1893 | + futures = "0.1" |
1894 | + generic-array = "0.12" |
1895 | + hex = "0.3" |
1896 | + http = "0.1" |
1897 | + human-size = "0.4" |
1898 | + humantime = "1" |
1899 | + hyper = "0.12" |
1900 | + linked-hash-map = { version = "0.5", features = ["serde_impl"] } |
1901 | + log = "0.4" |
1902 | + pretty_env_logger = "0.3" |
1903 | + serde = { version = "1", features = ["derive"] } |
1904 | + serde_json = "1" |
1905 | + sha2 = "0.8" |
1906 | + structopt = "0.2" |
1907 | + tokio = "0.1" |
1908 | + url = "1" |
1909 | + uuid = { version = "0.7", features = ["v4"] } |
1910 | + |
1911 | + [dependencies.rusoto_core] |
1912 | + version = "0.36" |
1913 | + default_features = false |
1914 | + features = ["rustls"] |
1915 | + |
1916 | + [dependencies.rusoto_s3] |
1917 | + version = "0.36" |
1918 | + default_features = false |
1919 | + features = ["rustls"] |
1920 | diff --git a/Dockerfile b/Dockerfile |
1921 | new file mode 100644 |
1922 | index 0000000..a8aa95b |
1923 | --- /dev/null |
1924 | +++ b/Dockerfile |
1925 | @@ -0,0 +1,50 @@ |
1926 | + FROM rust:1.33 as build |
1927 | + |
1928 | + ENV CARGO_BUILD_TARGET=x86_64-unknown-linux-musl |
1929 | + |
1930 | + ENV DEBIAN_FRONTEND=noninteractive |
1931 | + RUN \ |
1932 | + apt-get update && \ |
1933 | + apt-get -y install ca-certificates musl-tools && \ |
1934 | + rustup target add ${CARGO_BUILD_TARGET} |
1935 | + |
1936 | + # Use Tini as our PID 1. This will enable signals to be handled more correctly. |
1937 | + # |
1938 | + # Note that this can't be downloaded inside the scratch container as we have no |
1939 | + # chmod command. |
1940 | + # |
1941 | + # TODO: Use `--init` instead when it is more well-supported (this should be the |
1942 | + # case by Jan 1, 2020). |
1943 | + ENV TINI_VERSION v0.18.0 |
1944 | + ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini |
1945 | + RUN chmod +x /tini |
1946 | + |
1947 | + WORKDIR /source |
1948 | + |
1949 | + ENV PKG_CONFIG_ALLOW_CROSS=1 |
1950 | + |
1951 | + # Build the real project. |
1952 | + COPY ./ ./ |
1953 | + |
1954 | + RUN cargo build --release |
1955 | + |
1956 | + RUN \ |
1957 | + mkdir -p /build && \ |
1958 | + cp target/${CARGO_BUILD_TARGET}/release/rudolfs /build/ && \ |
1959 | + strip /build/rudolfs |
1960 | + |
1961 | + # Use scratch so we can get an itty-bitty-teeny-tiny image. This requires us to |
1962 | + # use musl when building the application. |
1963 | + FROM scratch |
1964 | + |
1965 | + EXPOSE 8080 |
1966 | + VOLUME ["/data"] |
1967 | + |
1968 | + COPY --from=build /tini /tini |
1969 | + |
1970 | + COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt |
1971 | + |
1972 | + COPY --from=build /build/ / |
1973 | + |
1974 | + ENTRYPOINT ["/tini", "--", "/rudolfs"] |
1975 | + CMD ["--cache-dir", "/data"] |
1976 | diff --git a/LICENSE b/LICENSE |
1977 | new file mode 100644 |
1978 | index 0000000..ddfd2cb |
1979 | --- /dev/null |
1980 | +++ b/LICENSE |
1981 | @@ -0,0 +1,18 @@ |
1982 | + Copyright (c) 2019 Jason White |
1983 | + |
1984 | + Permission is hereby granted, free of charge, to any person obtaining a copy of |
1985 | + this software and associated documentation files (the "Software"), to deal in |
1986 | + the Software without restriction, including without limitation the rights to |
1987 | + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
1988 | + the Software, and to permit persons to whom the Software is furnished to do so, |
1989 | + subject to the following conditions: |
1990 | + |
1991 | + The above copyright notice and this permission notice shall be included in all |
1992 | + copies or substantial portions of the Software. |
1993 | + |
1994 | + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
1995 | + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
1996 | + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
1997 | + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
1998 | + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
1999 | + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
2000 | diff --git a/README.md b/README.md |
2001 | new file mode 100644 |
2002 | index 0000000..524a0a4 |
2003 | --- /dev/null |
2004 | +++ b/README.md |
2005 | @@ -0,0 +1,142 @@ |
2006 | + # Rudo-LFS (aka "rudolfs") |
2007 | + |
2008 | + A high-performance, caching Git LFS server with an AWS S3 back-end. |
2009 | + |
2010 | + ## Features |
2011 | + |
2012 | + - AWS S3 permanent storage back-end. |
2013 | + |
2014 | + - A configurable local disk cache to speed up downloads (and reduce your |
2015 | + S3 bill). |
2016 | + |
2017 | + - Corruption-resilient local disk cache. Even if the disk is getting |
2018 | + blasted by cosmic rays, it'll find corrupted LFS objects and purge them from |
2019 | + the cache transparently. The client should never notice this happening. |
2020 | + |
2021 | + - Encryption of LFS objects in both the cache and in permanent storage. |
2022 | + |
2023 | + ## Running It |
2024 | + |
2025 | + ### Generate an encryption key |
2026 | + |
2027 | + All LFS objects are encrypted with the xchacha20 symmetric stream cipher. You |
2028 | + must generate a 32-byte encryption key before starting the server. |
2029 | + |
2030 | + Generating a random key is easy: |
2031 | + |
2032 | + openssl rand -hex 32 |
2033 | + |
2034 | + Keep this secret and save it in a password manager so you don't lose it. We will |
2035 | + pass this to the server below. |
2036 | + |
2037 | + **Note**: |
2038 | + - If the key ever changes, all existing LFS objects will become garbage. |
2039 | + When the Git LFS client attempts to download them, the SHA256 verification |
2040 | + step will fail. |
2041 | + - LFS objects in both the cache and in permanent storage are encrypted. |
2042 | + However, objects are decrypted before being sent to the LFS client, so take |
2043 | + any necessary precautions to keep your intellectual property safe. |
2044 | + |
2045 | + ### Development |
2046 | + |
2047 | + For testing during development, it is easiest to run it with Cargo. Create |
2048 | + a file called `test.sh` (this path is already ignored by `.gitignore`): |
2049 | + |
2050 | + ```bash |
2051 | + # Your AWS credentials. |
2052 | + export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX |
2053 | + export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
2054 | + export AWS_DEFAULT_REGION=us-west-1 |
2055 | + |
2056 | + # Change this to the output of `openssl rand -hex 32`. |
2057 | + KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
2058 | + |
2059 | + cargo run -- \ |
2060 | + --cache-dir cache \ |
2061 | + --host localhost:8080 \ |
2062 | + --s3-bucket foobar \ |
2063 | + --max-cache-size 10GiB \ |
2064 | + --key $KEY |
2065 | + ``` |
2066 | + |
2067 | + **Note**: Always use a different S3 bucket, cache directory, and encryption key |
2068 | + than what you use in your production environment. |
2069 | + |
2070 | + ### Production |
2071 | + |
2072 | + To run in a production environment, it is easiest to use `docker-compose`: |
2073 | + |
2074 | + 1. Create a `.env` file next to `docker-compose.yml` with the configuration |
2075 | + variables: |
2076 | + |
2077 | + ``` |
2078 | + AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXQ |
2079 | + AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
2080 | + AWS_DEFAULT_REGION=us-west-1 |
2081 | + LFS_ENCRYPTION_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
2082 | + LFS_S3_BUCKET=my-bucket |
2083 | + LFS_MAX_CACHE_SIZE=10GB |
2084 | + ``` |
2085 | + |
2086 | + 2. Use the provided `docker-compose.yml` file to run a production environment: |
2087 | + |
2088 | + ```bash |
2089 | + docker-compose up -d |
2090 | + ``` |
2091 | + |
2092 | + 3. **[Optional]**: It is best to use nginx as a reverse proxy for this server. |
2093 | + Use it to enable TLS. How to configure this is better covered by other |
2094 | + tutorials on the internet. |
2095 | + |
2096 | + **Note**: |
2097 | + - A bigger cache is (almost) always better. Try to use ~85% of the available |
2098 | + disk space. |
2099 | + - The cache data is stored in a Docker volume named `rudolfs_data`. If you |
2100 | + want to delete it, run `docker volume rm rudolfs_data`. |
2101 | + |
2102 | + ## AWS Credentials |
2103 | + |
2104 | + AWS credentials must be provided to the server so that it can make requests to |
2105 | + the S3 bucket specified on the command line (with `--s3-bucket`). |
2106 | + |
2107 | + Your AWS credentials will be searched for in the following order: |
2108 | + |
2109 | + 1. Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` |
2110 | + 2. AWS credentials file. Usually located at `~/.aws/credentials`. |
2111 | + 3. IAM instance profile. Will only work if running on an EC2 instance with an |
2112 | + instance profile/role. |
2113 | + |
2114 | + The AWS region is read from the `AWS_DEFAULT_REGION` or `AWS_REGION` environment |
2115 | + variable. If it is malformed, it will fall back to `us-east-1`. If it is not |
2116 | + present it will fall back on the value associated with the current profile in |
2117 | + `~/.aws/config` or the file specified by the `AWS_CONFIG_FILE` environment |
2118 | + variable. If that is malformed or absent it will fall back to `us-east-1`. |
2119 | + |
2120 | + ## Client Configuration |
2121 | + |
2122 | + Add `.lfsconfig` to the root of your Git repository: |
2123 | + |
2124 | + ``` |
2125 | + [lfs] |
2126 | + url = "http://gitlfs.example.com:8080/" |
2127 | + ``` |
2128 | + |
2129 | + Optionally, I also recommend changing these global settings to speed things up: |
2130 | + |
2131 | + ``` bash |
2132 | + # Increase the number of worker threads |
2133 | + git config --global lfs.concurrenttransfers 64 |
2134 | + |
2135 | + # Use a global LFS cache to make re-cloning faster |
2136 | + git config --global lfs.storage ~/.cache/lfs |
2137 | + ``` |
2138 | + |
2139 | + ## License |
2140 | + |
2141 | + [MIT License](/LICENSE) |
2142 | + |
2143 | + ## Thanks |
2144 | + |
2145 | + This was developed at [Environmental Systems Research |
2146 | + Institute](http://www.esri.com/) (Esri) who have graciously allowed me to retain |
2147 | + the copyright and publish it as open source software. |
2148 | diff --git a/docker-compose.yml b/docker-compose.yml |
2149 | new file mode 100644 |
2150 | index 0000000..84e4037 |
2151 | --- /dev/null |
2152 | +++ b/docker-compose.yml |
2153 | @@ -0,0 +1,48 @@ |
2154 | + # Copy this file to another location and modify as necessary. |
2155 | + version: '3' |
2156 | + services: |
2157 | + app: |
2158 | + #image: rudolfs:latest |
2159 | + build: |
2160 | + context: . |
2161 | + dockerfile: Dockerfile |
2162 | + ports: |
2163 | + - "8081:8080" |
2164 | + volumes: |
2165 | + - data:/data |
2166 | + restart: always |
2167 | + environment: |
2168 | + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} |
2169 | + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} |
2170 | + - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} |
2171 | + - LFS_ENCRYPTION_KEY=${LFS_ENCRYPTION_KEY} |
2172 | + - LFS_S3_BUCKET=${LFS_S3_BUCKET} |
2173 | + - LFS_MAX_CACHE_SIZE=${LFS_MAX_CACHE_SIZE} |
2174 | + entrypoint: |
2175 | + - /tini |
2176 | + - -- |
2177 | + - /rudolfs |
2178 | + - --cache-dir |
2179 | + - /data |
2180 | + - --key |
2181 | + - ${LFS_ENCRYPTION_KEY} |
2182 | + - --s3-bucket |
2183 | + - ${LFS_S3_BUCKET} |
2184 | + - --max-cache-size |
2185 | + - ${LFS_MAX_CACHE_SIZE} |
2186 | + |
2187 | + # A real production server should use nginx. How to configure this depends on |
2188 | + # your needs. Use your Google-search skills to configure this correctly. |
2189 | + # |
2190 | + # nginx: |
2191 | + # image: nginx:stable |
2192 | + # ports: |
2193 | + # - 80:80 |
2194 | + # - 443:443 |
2195 | + # volumes: |
2196 | + # - ./nginx.conf:/etc/nginx/nginx.conf |
2197 | + # - ./nginx/errors.log:/etc/nginx/errors.log |
2198 | + |
2199 | + |
2200 | + volumes: |
2201 | + data: |
2202 | diff --git a/rustfmt.toml b/rustfmt.toml |
2203 | new file mode 100644 |
2204 | index 0000000..2451809 |
2205 | --- /dev/null |
2206 | +++ b/rustfmt.toml |
2207 | @@ -0,0 +1,6 @@ |
2208 | + max_width = 80 |
2209 | + error_on_line_overflow = true |
2210 | + error_on_unformatted = true |
2211 | + normalize_comments = true |
2212 | + wrap_comments = true |
2213 | + license_template_path = ".license_template" |
2214 | diff --git a/src/app.rs b/src/app.rs |
2215 | new file mode 100644 |
2216 | index 0000000..4dfa7ed |
2217 | --- /dev/null |
2218 | +++ b/src/app.rs |
2219 | @@ -0,0 +1,429 @@ |
2220 | + // Copyright (c) 2019 Jason White |
2221 | + // |
2222 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
2223 | + // of this software and associated documentation files (the "Software"), to deal |
2224 | + // in the Software without restriction, including without limitation the rights |
2225 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
2226 | + // copies of the Software, and to permit persons to whom the Software is |
2227 | + // furnished to do so, subject to the following conditions: |
2228 | + // |
2229 | + // The above copyright notice and this permission notice shall be included in |
2230 | + // all copies or substantial portions of the Software. |
2231 | + // |
2232 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
2233 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
2234 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
2235 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
2236 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
2237 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
2238 | + // SOFTWARE. |
2239 | + use std::io; |
2240 | + use std::sync::Arc; |
2241 | + |
2242 | + use futures::{ |
2243 | + future::{self, Either}, |
2244 | + Future, IntoFuture, Stream, |
2245 | + }; |
2246 | + use http::{ |
2247 | + self, header, |
2248 | + uri::{Authority, Scheme}, |
2249 | + StatusCode, Uri, |
2250 | + }; |
2251 | + use hyper::{self, service::Service, Method, Request, Response, Chunk}; |
2252 | + |
2253 | + use crate::error::Error; |
2254 | + use crate::hyperext::{into_request, Body, IntoResponse, RequestExt}; |
2255 | + use crate::lfs; |
2256 | + use crate::storage::{LFSObject, Storage}; |
2257 | + |
2258 | + /// Shared state for all instances of the `App` service. |
2259 | + pub struct State<S> { |
2260 | + // Storage backend. |
2261 | + storage: S, |
2262 | + } |
2263 | + |
2264 | + impl<S> State<S> { |
2265 | + pub fn new(storage: S) -> Self { |
2266 | + State { storage } |
2267 | + } |
2268 | + } |
2269 | + |
2270 | + #[derive(Clone)] |
2271 | + pub struct App<S> { |
2272 | + state: Arc<State<S>>, |
2273 | + } |
2274 | + |
2275 | + impl<S> App<S> |
2276 | + where |
2277 | + S: Storage + Send + Sync + 'static, |
2278 | + S::Error: Into<Error> + 'static, |
2279 | + Error: From<S::Error>, |
2280 | + { |
2281 | + pub fn new(state: Arc<State<S>>) -> Self { |
2282 | + App { state } |
2283 | + } |
2284 | + |
2285 | + /// Downloads a single LFS object. |
2286 | + fn download( |
2287 | + &mut self, |
2288 | + _req: Request<Body>, |
2289 | + oid: lfs::Oid, |
2290 | + ) -> impl Future<Item = Response<Body>, Error = Error> { |
2291 | + self.state.storage.get(&oid).from_err::<Error>().and_then( |
2292 | + move |object| -> Result<_, Error> { |
2293 | + if let Some(object) = object { |
2294 | + return Response::builder() |
2295 | + .status(StatusCode::OK) |
2296 | + .header( |
2297 | + header::CONTENT_TYPE, |
2298 | + "application/octet-stream", |
2299 | + ) |
2300 | + .header(header::CONTENT_LENGTH, object.len()) |
2301 | + .body(Body::wrap_stream(object.stream())) |
2302 | + .map_err(Into::into); |
2303 | + } else { |
2304 | + return Response::builder() |
2305 | + .status(StatusCode::NOT_FOUND) |
2306 | + .body(Body::empty()) |
2307 | + .map_err(Into::into); |
2308 | + } |
2309 | + }, |
2310 | + ) |
2311 | + } |
2312 | + |
2313 | + /// Uploads a single LFS object. |
2314 | + fn upload( |
2315 | + &mut self, |
2316 | + req: Request<Body>, |
2317 | + oid: lfs::Oid, |
2318 | + ) -> <Self as Service>::Future { |
2319 | + let len = req |
2320 | + .headers() |
2321 | + .get("Content-Length") |
2322 | + .and_then(|v| v.to_str().ok()) |
2323 | + .and_then(|s| s.parse::<u64>().ok()); |
2324 | + |
2325 | + let len = match len { |
2326 | + Some(len) => len, |
2327 | + None => { |
2328 | + return Box::new( |
2329 | + Response::builder() |
2330 | + .status(StatusCode::BAD_REQUEST) |
2331 | + .body(Body::from("Invalid Content-Length header.")) |
2332 | + .map_err(Into::into) |
2333 | + .into_future(), |
2334 | + ); |
2335 | + } |
2336 | + }; |
2337 | + |
2338 | + // Verify the SHA256 of the uploaded object as it is being uploaded. |
2339 | + let stream = req |
2340 | + .into_body() |
2341 | + .map(Chunk::into_bytes) |
2342 | + .from_err::<Error>() |
2343 | + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)); |
2344 | + |
2345 | + let object = LFSObject::new(len, Box::new(stream)); |
2346 | + |
2347 | + Box::new( |
2348 | + self.state |
2349 | + .storage |
2350 | + .put(&oid, object) |
2351 | + .from_err::<Error>() |
2352 | + .and_then(|_| { |
2353 | + Response::builder() |
2354 | + .status(StatusCode::OK) |
2355 | + .body(Body::empty()) |
2356 | + .map_err(Into::into) |
2357 | + }), |
2358 | + ) |
2359 | + } |
2360 | + |
2361 | + /// Verifies that an LFS object exists on the server. |
2362 | + fn verify( |
2363 | + &mut self, |
2364 | + req: Request<Body>, |
2365 | + ) -> impl Future<Item = Response<Body>, Error = Error> { |
2366 | + let state = self.state.clone(); |
2367 | + |
2368 | + req.into_body() |
2369 | + .into_json() |
2370 | + .and_then(move |val: lfs::VerifyRequest| { |
2371 | + state.storage.size(&val.oid).from_err::<Error>().and_then( |
2372 | + move |size| { |
2373 | + if let Some(size) = size { |
2374 | + if size == val.size { |
2375 | + return Response::builder() |
2376 | + .status(StatusCode::OK) |
2377 | + .body(Body::empty()) |
2378 | + .map_err(Into::into); |
2379 | + } |
2380 | + } |
2381 | + |
2382 | + // Object doesn't exist or the size is incorrect. |
2383 | + Response::builder() |
2384 | + .status(StatusCode::NOT_FOUND) |
2385 | + .body(Body::empty()) |
2386 | + .map_err(Into::into) |
2387 | + }, |
2388 | + ) |
2389 | + }) |
2390 | + } |
2391 | + |
2392 | + /// Generates a "404 not found" response. |
2393 | + fn not_found( |
2394 | + &mut self, |
2395 | + _req: Request<Body>, |
2396 | + ) -> Result<Response<Body>, Error> { |
2397 | + Ok(Response::builder() |
2398 | + .status(StatusCode::NOT_FOUND) |
2399 | + .body("Not found".into())?) |
2400 | + } |
2401 | + |
2402 | + /// Batch API endpoint for the Git LFS server spec. |
2403 | + /// |
2404 | + /// See also: |
2405 | + /// https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md |
2406 | + fn batch( |
2407 | + &mut self, |
2408 | + req: Request<Body>, |
2409 | + ) -> impl Future<Item = Response<Body>, Error = Error> { |
2410 | + // Get the host name and scheme. |
2411 | + let uri = Uri::builder() |
2412 | + .scheme(req.scheme().unwrap_or(Scheme::HTTP)) |
2413 | + .authority( |
2414 | + req.authority() |
2415 | + .unwrap_or_else(|| Authority::from_static("localhost")), |
2416 | + ) |
2417 | + .path_and_query("/") |
2418 | + .build() |
2419 | + .unwrap(); |
2420 | + |
2421 | + let state = self.state.clone(); |
2422 | + |
2423 | + req.into_body().into_json().then( |
2424 | + move |result: Result<lfs::BatchRequest, _>| { |
2425 | + match result { |
2426 | + Ok(val) => { |
2427 | + let operation = val.operation; |
2428 | + |
2429 | + // For each object, check if it exists in the storage |
2430 | + // backend. |
2431 | + let objects = |
2432 | + val.objects.into_iter().map(move |object| { |
2433 | + let uri = uri.clone(); |
2434 | + |
2435 | + state.storage.size(&object.oid).map( |
2436 | + move |size| { |
2437 | + basic_response( |
2438 | + uri, object, operation, size, |
2439 | + ) |
2440 | + }, |
2441 | + ) |
2442 | + }); |
2443 | + |
2444 | + Either::A( |
2445 | + future::join_all(objects) |
2446 | + .from_err::<Error>() |
2447 | + .and_then(|objects| { |
2448 | + let response = lfs::BatchResponse { |
2449 | + transfer: Some(lfs::Transfer::Basic), |
2450 | + objects, |
2451 | + }; |
2452 | + |
2453 | + Response::builder() |
2454 | + .status(StatusCode::OK) |
2455 | + .header( |
2456 | + header::CONTENT_TYPE, |
2457 | + "application/json", |
2458 | + ) |
2459 | + .body(Body::json(&response)?) |
2460 | + .map_err(Into::into) |
2461 | + }), |
2462 | + ) |
2463 | + } |
2464 | + Err(err) => { |
2465 | + let response = lfs::BatchResponseError { |
2466 | + message: err.to_string(), |
2467 | + documentation_url: None, |
2468 | + request_id: None, |
2469 | + }; |
2470 | + |
2471 | + Either::B( |
2472 | + Response::builder() |
2473 | + .status(StatusCode::BAD_REQUEST) |
2474 | + .body(Body::json(&response).unwrap()) |
2475 | + .map_err(Into::into) |
2476 | + .into_future(), |
2477 | + ) |
2478 | + } |
2479 | + } |
2480 | + }, |
2481 | + ) |
2482 | + } |
2483 | + } |
2484 | + |
2485 | + fn basic_response( |
2486 | + uri: Uri, |
2487 | + object: lfs::RequestObject, |
2488 | + op: lfs::Operation, |
2489 | + size: Option<u64>, |
2490 | + ) -> lfs::ResponseObject { |
2491 | + if let Some(size) = size { |
2492 | + // Ensure that the client and server agree on the size of the object. |
2493 | + if object.size != size { |
2494 | + return lfs::ResponseObject { |
2495 | + oid: object.oid, |
2496 | + size, |
2497 | + error: Some(lfs::ObjectError { |
2498 | + code: 400, |
2499 | + message: format!( |
2500 | + "bad object size: requested={}, actual={}", |
2501 | + object.size, size |
2502 | + ), |
2503 | + }), |
2504 | + authenticated: None, |
2505 | + actions: None, |
2506 | + }; |
2507 | + } |
2508 | + } |
2509 | + |
2510 | + let href = format!("{}object/{}", uri, object.oid); |
2511 | + |
2512 | + let action = lfs::Action { |
2513 | + href, |
2514 | + header: None, |
2515 | + expires_in: None, |
2516 | + expires_at: None, |
2517 | + }; |
2518 | + |
2519 | + match op { |
2520 | + lfs::Operation::Upload => { |
2521 | + // If the object does exist, then we should not return any action. |
2522 | + // |
2523 | + // If the object does not exist, then we should return an upload |
2524 | + // action. |
2525 | + match size { |
2526 | + Some(size) => lfs::ResponseObject { |
2527 | + oid: object.oid, |
2528 | + size, |
2529 | + error: None, |
2530 | + authenticated: Some(true), |
2531 | + actions: None, |
2532 | + }, |
2533 | + None => lfs::ResponseObject { |
2534 | + oid: object.oid, |
2535 | + size: object.size, |
2536 | + error: None, |
2537 | + authenticated: Some(true), |
2538 | + actions: Some(lfs::Actions { |
2539 | + download: None, |
2540 | + upload: Some(action.clone()), |
2541 | + verify: Some(lfs::Action { |
2542 | + href: format!("{}objects/verify", uri), |
2543 | + header: None, |
2544 | + expires_in: None, |
2545 | + expires_at: None, |
2546 | + }), |
2547 | + }), |
2548 | + }, |
2549 | + } |
2550 | + } |
2551 | + lfs::Operation::Download => { |
2552 | + // If the object does not exist, then we should return a 404 error |
2553 | + // for this object. |
2554 | + match size { |
2555 | + Some(size) => lfs::ResponseObject { |
2556 | + oid: object.oid, |
2557 | + size, |
2558 | + error: None, |
2559 | + authenticated: Some(true), |
2560 | + actions: Some(lfs::Actions { |
2561 | + download: Some(action), |
2562 | + upload: None, |
2563 | + verify: None, |
2564 | + }), |
2565 | + }, |
2566 | + None => lfs::ResponseObject { |
2567 | + oid: object.oid, |
2568 | + size: object.size, |
2569 | + error: Some(lfs::ObjectError { |
2570 | + code: 404, |
2571 | + message: "not found".into(), |
2572 | + }), |
2573 | + authenticated: Some(true), |
2574 | + actions: None, |
2575 | + }, |
2576 | + } |
2577 | + } |
2578 | + } |
2579 | + } |
2580 | + |
2581 | + impl<S> Service for App<S> |
2582 | + where |
2583 | + S: Storage + Send + Sync + 'static, |
2584 | + S::Error: Into<Error> + 'static, |
2585 | + Error: From<S::Error>, |
2586 | + { |
2587 | + type ReqBody = hyper::Body; |
2588 | + type ResBody = Body; |
2589 | + type Error = Error; |
2590 | + type Future = Box<dyn Future<Item = Response<Body>, Error = Error> + Send>; |
2591 | + |
2592 | + fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future { |
2593 | + let req = into_request(req); |
2594 | + |
2595 | + if req.uri().path().starts_with("/object/") { |
2596 | + // Extract the OID from the request. |
2597 | + let mut parts = |
2598 | + req.uri().path().split('/').filter(|s| !s.is_empty()); |
2599 | + |
2600 | + // Skip over the 'object/' part. |
2601 | + parts.next(); |
2602 | + |
2603 | + let oid = match parts.next() { |
2604 | + Some(oid) => oid, |
2605 | + None => { |
2606 | + return Response::builder() |
2607 | + .status(StatusCode::BAD_REQUEST) |
2608 | + .body(Body::from("Missing OID parameter.")) |
2609 | + .map_err(Into::into) |
2610 | + .response(); |
2611 | + } |
2612 | + }; |
2613 | + |
2614 | + let oid = match oid.parse::<lfs::Oid>() { |
2615 | + Ok(oid) => oid, |
2616 | + Err(err) => { |
2617 | + return Response::builder() |
2618 | + .status(StatusCode::BAD_REQUEST) |
2619 | + .body(Body::from(format!("Invalid OID: {}", err))) |
2620 | + .map_err(Into::into) |
2621 | + .response(); |
2622 | + } |
2623 | + }; |
2624 | + |
2625 | + match *req.method() { |
2626 | + Method::GET => return self.download(req, oid).response(), |
2627 | + Method::PUT => return self.upload(req, oid), |
2628 | + _ => return self.not_found(req).response(), |
2629 | + } |
2630 | + } |
2631 | + |
2632 | + match (req.method(), req.uri().path()) { |
2633 | + (&Method::POST, "/objects/batch") => { |
2634 | + return self.batch(req).response(); |
2635 | + } |
2636 | + (&Method::POST, "/objects/verify") => { |
2637 | + return self.verify(req).response(); |
2638 | + } |
2639 | + _ => {} |
2640 | + } |
2641 | + |
2642 | + Response::builder() |
2643 | + .status(StatusCode::NOT_FOUND) |
2644 | + .body(Body::empty()) |
2645 | + .map_err(Into::into) |
2646 | + .response() |
2647 | + } |
2648 | + } |
2649 | diff --git a/src/error.rs b/src/error.rs |
2650 | new file mode 100644 |
2651 | index 0000000..419087d |
2652 | --- /dev/null |
2653 | +++ b/src/error.rs |
2654 | @@ -0,0 +1,43 @@ |
2655 | + // Copyright (c) 2019 Jason White |
2656 | + // |
2657 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
2658 | + // of this software and associated documentation files (the "Software"), to deal |
2659 | + // in the Software without restriction, including without limitation the rights |
2660 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
2661 | + // copies of the Software, and to permit persons to whom the Software is |
2662 | + // furnished to do so, subject to the following conditions: |
2663 | + // |
2664 | + // The above copyright notice and this permission notice shall be included in |
2665 | + // all copies or substantial portions of the Software. |
2666 | + // |
2667 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
2668 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
2669 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
2670 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
2671 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
2672 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
2673 | + // SOFTWARE. |
2674 | + use std::io; |
2675 | + |
2676 | + use derive_more::{Display, From}; |
2677 | + use http; |
2678 | + use hyper; |
2679 | + use serde_json; |
2680 | + |
2681 | + use crate::sha256::{Sha256Error, Sha256VerifyError}; |
2682 | + use crate::storage::{self, Storage}; |
2683 | + |
2684 | + // Define a type so we can return multiple types of errors |
2685 | + #[derive(Debug, From, Display)] |
2686 | + pub enum Error { |
2687 | + Io(io::Error), |
2688 | + Http(http::Error), |
2689 | + Hyper(hyper::Error), |
2690 | + Json(serde_json::Error), |
2691 | + Sha256Error(Sha256Error), |
2692 | + Sha256VerifyError(Sha256VerifyError), |
2693 | + S3(storage::S3Error), |
2694 | + S3DiskCache(<storage::S3DiskCache as Storage>::Error), |
2695 | + } |
2696 | + |
2697 | + impl std::error::Error for Error {} |
2698 | diff --git a/src/hyperext.rs b/src/hyperext.rs |
2699 | new file mode 100644 |
2700 | index 0000000..c6cdc80 |
2701 | --- /dev/null |
2702 | +++ b/src/hyperext.rs |
2703 | @@ -0,0 +1,218 @@ |
2704 | + // Copyright (c) 2019 Jason White |
2705 | + // |
2706 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
2707 | + // of this software and associated documentation files (the "Software"), to deal |
2708 | + // in the Software without restriction, including without limitation the rights |
2709 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
2710 | + // copies of the Software, and to permit persons to whom the Software is |
2711 | + // furnished to do so, subject to the following conditions: |
2712 | + // |
2713 | + // The above copyright notice and this permission notice shall be included in |
2714 | + // all copies or substantial portions of the Software. |
2715 | + // |
2716 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
2717 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
2718 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
2719 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
2720 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
2721 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
2722 | + // SOFTWARE. |
2723 | + use std::ops::Deref; |
2724 | + |
2725 | + use derive_more::From; |
2726 | + use futures::{Future, IntoFuture, Poll, Stream}; |
2727 | + use http::uri::{Authority, Scheme}; |
2728 | + use hyper::{ |
2729 | + body::{Payload, Sender}, |
2730 | + Body as HBody, Chunk, HeaderMap, Request, Response, |
2731 | + }; |
2732 | + use serde::{Deserialize, Serialize}; |
2733 | + |
2734 | + use crate::error::Error; |
2735 | + |
2736 | + #[derive(Debug, Default, From)] |
2737 | + pub struct Body(HBody); |
2738 | + |
2739 | + impl Body { |
2740 | + pub fn empty() -> Self { |
2741 | + HBody::empty().into() |
2742 | + } |
2743 | + |
2744 | + #[allow(unused)] |
2745 | + pub fn channel() -> (Sender, Self) { |
2746 | + let (sender, body) = HBody::channel(); |
2747 | + (sender, body.into()) |
2748 | + } |
2749 | + |
2750 | + /// Deserializes the body as json. Returns a future of the deserialized |
2751 | + /// json. |
2752 | + pub fn into_json<T>(self) -> impl Future<Item = T, Error = Error> |
2753 | + where |
2754 | + T: for<'de> Deserialize<'de>, |
2755 | + { |
2756 | + self.concat2() |
2757 | + .from_err::<Error>() |
2758 | + .and_then(|body| Ok(serde_json::from_slice(&body)?)) |
2759 | + .from_err() |
2760 | + } |
2761 | + |
2762 | + /// Serialize a value to JSON. |
2763 | + pub fn json<T>(value: &T) -> Result<Self, Error> |
2764 | + where |
2765 | + T: Serialize, |
2766 | + { |
2767 | + let body: HBody = serde_json::to_vec_pretty(&value)?.into(); |
2768 | + Ok(body.into()) |
2769 | + } |
2770 | + |
2771 | + pub fn wrap_stream<S>(stream: S) -> Self |
2772 | + where |
2773 | + S: Stream + Send + 'static, |
2774 | + S::Error: Into<Box<dyn std::error::Error + Send + Sync>>, |
2775 | + Chunk: From<S::Item>, |
2776 | + { |
2777 | + Body(HBody::wrap_stream(stream)) |
2778 | + } |
2779 | + } |
2780 | + |
2781 | + impl Into<HBody> for Body { |
2782 | + fn into(self) -> HBody { |
2783 | + self.0 |
2784 | + } |
2785 | + } |
2786 | + |
2787 | + impl From<String> for Body { |
2788 | + fn from(s: String) -> Self { |
2789 | + Body(s.into()) |
2790 | + } |
2791 | + } |
2792 | + |
2793 | + impl From<&'static str> for Body { |
2794 | + fn from(slice: &'static str) -> Self { |
2795 | + Body(slice.into()) |
2796 | + } |
2797 | + } |
2798 | + |
2799 | + type ChunkedStream = Box< |
2800 | + dyn Stream< |
2801 | + Item = Chunk, |
2802 | + Error = Box<dyn std::error::Error + 'static + Sync + Send>, |
2803 | + > |
2804 | + + 'static |
2805 | + + Send, |
2806 | + >; |
2807 | + |
2808 | + impl From<ChunkedStream> for Body { |
2809 | + fn from(stream: ChunkedStream) -> Self { |
2810 | + Body(stream.into()) |
2811 | + } |
2812 | + } |
2813 | + |
2814 | + impl Deref for Body { |
2815 | + type Target = HBody; |
2816 | + |
2817 | + fn deref(&self) -> &Self::Target { |
2818 | + &self.0 |
2819 | + } |
2820 | + } |
2821 | + |
2822 | + impl Stream for Body { |
2823 | + type Item = <HBody as Stream>::Item; |
2824 | + type Error = <HBody as Stream>::Error; |
2825 | + |
2826 | + fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { |
2827 | + self.0.poll() |
2828 | + } |
2829 | + } |
2830 | + |
2831 | + impl Payload for Body { |
2832 | + type Data = <HBody as Payload>::Data; |
2833 | + type Error = <HBody as Payload>::Error; |
2834 | + |
2835 | + fn poll_data(&mut self) -> Poll<Option<Self::Data>, Self::Error> { |
2836 | + <HBody as Payload>::poll_data(&mut self.0) |
2837 | + } |
2838 | + |
2839 | + fn poll_trailers(&mut self) -> Poll<Option<HeaderMap>, Self::Error> { |
2840 | + <HBody as Payload>::poll_trailers(&mut self.0) |
2841 | + } |
2842 | + |
2843 | + fn is_end_stream(&self) -> bool { |
2844 | + <HBody as Payload>::is_end_stream(&self.0) |
2845 | + } |
2846 | + |
2847 | + fn content_length(&self) -> Option<u64> { |
2848 | + <HBody as Payload>::content_length(&self.0) |
2849 | + } |
2850 | + } |
2851 | + |
2852 | + type BoxResponse<T> = Box<dyn Future<Item = Response<T>, Error = Error> + Send>; |
2853 | + |
2854 | + pub trait IntoResponse<T> { |
2855 | + /// Convert something into a response. |
2856 | + fn response(self) -> BoxResponse<T>; |
2857 | + } |
2858 | + |
2859 | + impl<F, T, U> IntoResponse<U> for F |
2860 | + where |
2861 | + F: IntoFuture<Item = Response<T>, Error = Error>, |
2862 | + F::Future: Send + 'static, |
2863 | + T: Into<U>, |
2864 | + { |
2865 | + fn response(self) -> BoxResponse<U> { |
2866 | + Box::new( |
2867 | + self.into_future() |
2868 | + .map(move |r| -> Response<U> { into_response(r) }), |
2869 | + ) |
2870 | + } |
2871 | + } |
2872 | + |
2873 | + pub fn into_request<T, U>(req: Request<T>) -> Request<U> |
2874 | + where |
2875 | + T: Into<U>, |
2876 | + { |
2877 | + let (parts, body) = req.into_parts(); |
2878 | + Request::from_parts(parts, body.into()) |
2879 | + } |
2880 | + |
2881 | + fn into_response<T, U>(res: Response<T>) -> Response<U> |
2882 | + where |
2883 | + T: Into<U>, |
2884 | + { |
2885 | + let (parts, body) = res.into_parts(); |
2886 | + Response::from_parts(parts, body.into()) |
2887 | + } |
2888 | + |
2889 | + pub trait RequestExt { |
2890 | + /// Gets the scheme based on the headers in the request. |
2891 | + /// |
2892 | + /// There's no good way to determine the scheme for HTTP/1, so we must defer |
2893 | + /// to other methods in that case (such as from a server configuration |
2894 | + /// file). |
2895 | + /// |
2896 | + /// HTTP/2 has the `:scheme` header that can tell us the scheme. |
2897 | + fn scheme(&self) -> Option<Scheme>; |
2898 | + |
2899 | + /// Gets the authority based on the headers in the request. |
2900 | + /// |
2901 | + /// First checks the HTTP/2 header `:athority` and then falls back to the |
2902 | + /// `Host` header. |
2903 | + fn authority(&self) -> Option<Authority>; |
2904 | + } |
2905 | + |
2906 | + impl<B> RequestExt for Request<B> { |
2907 | + fn scheme(&self) -> Option<Scheme> { |
2908 | + self.headers().get(":scheme").and_then(|scheme| { |
2909 | + Scheme::from_shared(scheme.as_bytes().into()).ok() |
2910 | + }) |
2911 | + } |
2912 | + |
2913 | + fn authority(&self) -> Option<Authority> { |
2914 | + self.headers() |
2915 | + .get(":authority") |
2916 | + .or_else(|| self.headers().get("Host")) |
2917 | + .and_then(|authority| { |
2918 | + Authority::from_shared(authority.as_bytes().into()).ok() |
2919 | + }) |
2920 | + } |
2921 | + } |
2922 | diff --git a/src/lfs.rs b/src/lfs.rs |
2923 | new file mode 100644 |
2924 | index 0000000..f6e7b6a |
2925 | --- /dev/null |
2926 | +++ b/src/lfs.rs |
2927 | @@ -0,0 +1,194 @@ |
2928 | + // Copyright (c) 2019 Jason White |
2929 | + // |
2930 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
2931 | + // of this software and associated documentation files (the "Software"), to deal |
2932 | + // in the Software without restriction, including without limitation the rights |
2933 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
2934 | + // copies of the Software, and to permit persons to whom the Software is |
2935 | + // furnished to do so, subject to the following conditions: |
2936 | + // |
2937 | + // The above copyright notice and this permission notice shall be included in |
2938 | + // all copies or substantial portions of the Software. |
2939 | + // |
2940 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
2941 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
2942 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
2943 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
2944 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
2945 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
2946 | + // SOFTWARE. |
2947 | + use std::collections::BTreeMap; |
2948 | + |
2949 | + use serde::{Deserialize, Serialize}; |
2950 | + |
2951 | + use crate::sha256::Sha256; |
2952 | + |
2953 | + pub type Oid = Sha256; |
2954 | + |
2955 | + #[derive(Copy, Clone, Debug, Serialize, Deserialize)] |
2956 | + #[serde(rename_all = "lowercase")] |
2957 | + pub enum Operation { |
2958 | + Upload, |
2959 | + Download, |
2960 | + } |
2961 | + |
2962 | + /// A transfer adaptor. |
2963 | + #[derive(Clone, Debug, Serialize, Deserialize)] |
2964 | + #[serde(rename_all = "lowercase")] |
2965 | + pub enum Transfer { |
2966 | + /// Basic transfer adaptor. |
2967 | + Basic, |
2968 | + } |
2969 | + |
2970 | + /// An LFS object in a request. |
2971 | + #[derive(Clone, Debug, Serialize, Deserialize)] |
2972 | + pub struct RequestObject { |
2973 | + /// String OID of the LFS object. |
2974 | + pub oid: Oid, |
2975 | + |
2976 | + /// Integer byte size of the LFS object. |
2977 | + pub size: u64, |
2978 | + } |
2979 | + |
2980 | + #[derive(Clone, Default, Debug, Serialize, Deserialize)] |
2981 | + pub struct Action { |
2982 | + /// URL to hit for the object. |
2983 | + pub href: String, |
2984 | + |
2985 | + /// Optional hash of string HTTP header key/value pairs to apply to the |
2986 | + /// request. |
2987 | + pub header: Option<BTreeMap<String, String>>, |
2988 | + |
2989 | + /// Whole number of seconds after local client time when transfer will |
2990 | + /// expire. Preferred over `expires_at` if both are provided. |
2991 | + #[serde(skip_serializing_if = "Option::is_none")] |
2992 | + pub expires_in: Option<i32>, |
2993 | + |
2994 | + /// String ISO 8601 formatted timestamp for when the given action expires |
2995 | + /// (usually due to a temporary token). |
2996 | + #[serde(skip_serializing_if = "Option::is_none")] |
2997 | + pub expires_at: Option<String>, |
2998 | + } |
2999 | + |
3000 | + #[derive(Debug, Serialize, Deserialize)] |
3001 | + pub struct ObjectError { |
3002 | + /// HTTP response code. |
3003 | + pub code: u32, |
3004 | + |
3005 | + /// Error message. |
3006 | + pub message: String, |
3007 | + } |
3008 | + |
3009 | + #[derive(Debug, Serialize, Deserialize)] |
3010 | + pub struct Actions { |
3011 | + /// A download action. The action href is to receive a GET request. The |
3012 | + /// response contains the LFS object data. |
3013 | + #[serde(skip_serializing_if = "Option::is_none")] |
3014 | + pub download: Option<Action>, |
3015 | + |
3016 | + /// An upload action. The action href is to receive a PUT request with the |
3017 | + /// LFS object data. |
3018 | + #[serde(skip_serializing_if = "Option::is_none")] |
3019 | + pub upload: Option<Action>, |
3020 | + |
3021 | + /// A verify action. The action href is to receive a POST request after |
3022 | + /// a successful upload. |
3023 | + #[serde(skip_serializing_if = "Option::is_none")] |
3024 | + pub verify: Option<Action>, |
3025 | + } |
3026 | + |
3027 | + #[derive(Debug, Serialize, Deserialize)] |
3028 | + pub struct ResponseObject { |
3029 | + /// String OID of the LFS object. |
3030 | + pub oid: Oid, |
3031 | + |
3032 | + /// Integer byte size of the LFS object. |
3033 | + pub size: u64, |
3034 | + |
3035 | + /// An error. |
3036 | + #[serde(skip_serializing_if = "Option::is_none")] |
3037 | + pub error: Option<ObjectError>, |
3038 | + |
3039 | + /// Optional boolean specifying whether the request for this specific |
3040 | + /// object is authenticated. If ommitted or `false`, Git LFS will |
3041 | + /// attempt to find credentials for this URL. |
3042 | + #[serde(skip_serializing_if = "Option::is_none")] |
3043 | + pub authenticated: Option<bool>, |
3044 | + |
3045 | + /// Object containing the next actions for this object. Applicable actions |
3046 | + /// depend on which `operation` is specified in the request. How these |
3047 | + /// properties are interpreted depends on which transfer adapter the client |
3048 | + /// will be using. |
3049 | + /// |
3050 | + /// This will be `None` if there are no actions to perform. This will be |
3051 | + /// the case for upload operations if the server already has the |
3052 | + /// objects. |
3053 | + #[serde(skip_serializing_if = "Option::is_none")] |
3054 | + pub actions: Option<Actions>, |
3055 | + } |
3056 | + |
3057 | + /// A batch request. |
3058 | + #[derive(Debug, Serialize, Deserialize)] |
3059 | + pub struct BatchRequest { |
3060 | + /// The operation being performed. |
3061 | + pub operation: Operation, |
3062 | + |
3063 | + /// An optional array of string identifiers for transfer adapters that the |
3064 | + /// client has configured. If ommitted, the `basic` transfer adaptor *must* |
3065 | + /// be assumed by the server. |
3066 | + /// |
3067 | + /// Note: Git LFS currently only supports the `basic` transfer adapter. |
3068 | + /// This property was added for future compatibility with some experimental |
3069 | + /// tranfer adapters. |
3070 | + #[serde(skip_serializing_if = "Option::is_none")] |
3071 | + pub transfers: Option<Vec<Transfer>>, |
3072 | + |
3073 | + /// Optional object describing the server ref that the objects belong to. |
3074 | + /// |
3075 | + /// Note: Added in v2.4. |
3076 | + #[serde(rename = "ref")] |
3077 | + #[serde(skip_serializing_if = "Option::is_none")] |
3078 | + pub refs: Option<BTreeMap<String, String>>, |
3079 | + |
3080 | + /// An array of objects to upload/download. |
3081 | + pub objects: Vec<RequestObject>, |
3082 | + } |
3083 | + |
3084 | + #[derive(Debug, Serialize, Deserialize)] |
3085 | + pub struct BatchResponse { |
3086 | + /// String identifier of the transfer adapter that the server prefers. This |
3087 | + /// *must* be one of the given `transfer` identifiers from the request. |
3088 | + /// Servers can assume the `basic` transfer adaptor if `None` was given. |
3089 | + #[serde(skip_serializing_if = "Option::is_none")] |
3090 | + pub transfer: Option<Transfer>, |
3091 | + |
3092 | + /// An array of objects to download or upload. |
3093 | + pub objects: Vec<ResponseObject>, |
3094 | + } |
3095 | + |
3096 | + /// An error response. |
3097 | + #[derive(Debug, Serialize, Deserialize)] |
3098 | + pub struct BatchResponseError { |
3099 | + /// The error message. |
3100 | + pub message: String, |
3101 | + |
3102 | + /// Optional string to give the user a place to report errors. |
3103 | + #[serde(skip_serializing_if = "Option::is_none")] |
3104 | + pub documentation_url: Option<String>, |
3105 | + |
3106 | + /// Optional string unique identifier for the request. Useful for |
3107 | + /// debugging. |
3108 | + #[serde(skip_serializing_if = "Option::is_none")] |
3109 | + pub request_id: Option<String>, |
3110 | + } |
3111 | + |
3112 | + /// A request to verify an LFS object. |
3113 | + #[derive(Debug, Serialize, Deserialize)] |
3114 | + pub struct VerifyRequest { |
3115 | + /// Object ID. |
3116 | + pub oid: Oid, |
3117 | + |
3118 | + /// Size of the object. If this doesn't match the size of the real object, |
3119 | + /// then an error code should be returned. |
3120 | + pub size: u64, |
3121 | + } |
3122 | diff --git a/src/logger.rs b/src/logger.rs |
3123 | new file mode 100644 |
3124 | index 0000000..cfb85b3 |
3125 | --- /dev/null |
3126 | +++ b/src/logger.rs |
3127 | @@ -0,0 +1,89 @@ |
3128 | + // Copyright (c) 2019 Jason White |
3129 | + // |
3130 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
3131 | + // of this software and associated documentation files (the "Software"), to deal |
3132 | + // in the Software without restriction, including without limitation the rights |
3133 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
3134 | + // copies of the Software, and to permit persons to whom the Software is |
3135 | + // furnished to do so, subject to the following conditions: |
3136 | + // |
3137 | + // The above copyright notice and this permission notice shall be included in |
3138 | + // all copies or substantial portions of the Software. |
3139 | + // |
3140 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
3141 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
3142 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
3143 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
3144 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
3145 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
3146 | + // SOFTWARE. |
3147 | + use std::fmt; |
3148 | + use std::net::SocketAddr; |
3149 | + use std::time::Instant; |
3150 | + |
3151 | + use futures::Future; |
3152 | + use humantime::format_duration; |
3153 | + use hyper::{service::Service, Request, Response}; |
3154 | + use log; |
3155 | + |
3156 | + /// Wraps a service to provide logging on both the request and the response. |
3157 | + pub struct Logger<S> { |
3158 | + remote_addr: SocketAddr, |
3159 | + service: S, |
3160 | + } |
3161 | + |
3162 | + impl<S> Logger<S> { |
3163 | + pub fn new(remote_addr: SocketAddr, service: S) -> Self { |
3164 | + Logger { |
3165 | + remote_addr, |
3166 | + service, |
3167 | + } |
3168 | + } |
3169 | + } |
3170 | + |
3171 | + impl<S> Service for Logger<S> |
3172 | + where |
3173 | + S: Service, |
3174 | + S::Future: Send + 'static, |
3175 | + S::Error: fmt::Display + Send + 'static, |
3176 | + { |
3177 | + type ReqBody = S::ReqBody; |
3178 | + type ResBody = S::ResBody; |
3179 | + type Error = S::Error; |
3180 | + type Future = Box< |
3181 | + dyn Future<Item = Response<Self::ResBody>, Error = Self::Error> + Send, |
3182 | + >; |
3183 | + |
3184 | + fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future { |
3185 | + let method = req.method().clone(); |
3186 | + let uri = req.uri().clone(); |
3187 | + let remote_addr = self.remote_addr; |
3188 | + |
3189 | + let start = Instant::now(); |
3190 | + |
3191 | + Box::new(self.service.call(req).then(move |response| { |
3192 | + // TODO: Add a duration of how long it took to respond to the |
3193 | + // request. |
3194 | + match &response { |
3195 | + Ok(response) => log::info!( |
3196 | + "[{}] {} {} - {} ({})", |
3197 | + remote_addr.ip(), |
3198 | + method, |
3199 | + uri, |
3200 | + response.status(), |
3201 | + format_duration(start.elapsed()), |
3202 | + ), |
3203 | + Err(err) => log::error!( |
3204 | + "[{}] {} {} - {} ({})", |
3205 | + remote_addr.ip(), |
3206 | + method, |
3207 | + uri, |
3208 | + err, |
3209 | + format_duration(start.elapsed()), |
3210 | + ), |
3211 | + }; |
3212 | + |
3213 | + response |
3214 | + })) |
3215 | + } |
3216 | + } |
3217 | diff --git a/src/lru.rs b/src/lru.rs |
3218 | new file mode 100644 |
3219 | index 0000000..00364ae |
3220 | --- /dev/null |
3221 | +++ b/src/lru.rs |
3222 | @@ -0,0 +1,148 @@ |
3223 | + // Copyright (c) 2019 Jason White |
3224 | + // |
3225 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
3226 | + // of this software and associated documentation files (the "Software"), to deal |
3227 | + // in the Software without restriction, including without limitation the rights |
3228 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
3229 | + // copies of the Software, and to permit persons to whom the Software is |
3230 | + // furnished to do so, subject to the following conditions: |
3231 | + // |
3232 | + // The above copyright notice and this permission notice shall be included in |
3233 | + // all copies or substantial portions of the Software. |
3234 | + // |
3235 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
3236 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
3237 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
3238 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
3239 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
3240 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
3241 | + // SOFTWARE. |
3242 | + use futures::{Future, Stream}; |
3243 | + use linked_hash_map::LinkedHashMap; |
3244 | + |
3245 | + use crate::lfs::Oid; |
3246 | + |
3247 | + /// A least recently used (LRU) cache. |
3248 | + pub struct Cache { |
3249 | + // A linked hash map is used to implement an efficient LRU cache. |
3250 | + map: LinkedHashMap<Oid, u64>, |
3251 | + |
3252 | + // Total size of the cache. This is equal to the sum of the values in the |
3253 | + // map. We use this to determine if the cache has grown too large and must |
3254 | + // be pruned. |
3255 | + size: u64, |
3256 | + } |
3257 | + |
3258 | + impl Cache { |
3259 | + /// Creates a new, empty cache. |
3260 | + pub fn new() -> Self { |
3261 | + Cache { |
3262 | + map: LinkedHashMap::new(), |
3263 | + size: 0, |
3264 | + } |
3265 | + } |
3266 | + |
3267 | + /// Returns the size (in bytes) of the cache. |
3268 | + pub fn size(&self) -> u64 { |
3269 | + self.size |
3270 | + } |
3271 | + |
3272 | + /// Loads the cache from a stream of entries. Note that this throws away any |
3273 | + /// LRU information. Since the server shouldn't be restarted very often, |
3274 | + /// this shouldn't be a problem in practice. Frequently used entries will |
3275 | + /// naturally bubble back up to the top. |
3276 | + pub fn from_stream<S>( |
3277 | + stream: S, |
3278 | + ) -> impl Future<Item = Self, Error = S::Error> |
3279 | + where |
3280 | + S: Stream<Item = (Oid, u64)>, |
3281 | + { |
3282 | + stream |
3283 | + .fold(Cache::new(), move |mut cache, (oid, len)| { |
3284 | + cache.push(oid, len); |
3285 | + Ok(cache) |
3286 | + }) |
3287 | + .or_else(move |_err| Ok(Cache::new())) |
3288 | + } |
3289 | + |
3290 | + /// Removes the least recently used item. Returns `None` if the cache is |
3291 | + /// empty. When the cache gets too large, this should be called in a loop in |
3292 | + /// order to bring it below the threshold. |
3293 | + pub fn pop(&mut self) -> Option<(Oid, u64)> { |
3294 | + if let Some((k, v)) = self.map.pop_front() { |
3295 | + self.size -= v; |
3296 | + Some((k, v)) |
3297 | + } else { |
3298 | + None |
3299 | + } |
3300 | + } |
3301 | + |
3302 | + /// Removes an entry from the cache. Returns the size of the object if it |
3303 | + /// exists, or `None` if it didn't exist in the cache. |
3304 | + pub fn remove(&mut self, key: &Oid) -> Option<u64> { |
3305 | + self.map.remove(key).map(|size| { |
3306 | + self.size -= size; |
3307 | + size |
3308 | + }) |
3309 | + } |
3310 | + |
3311 | + /// Gets an entry by key without perturbing the LRU ordering. |
3312 | + pub fn get(&self, key: &Oid) -> Option<u64> { |
3313 | + self.map.get(key).cloned() |
3314 | + } |
3315 | + |
3316 | + /// Gets an entry by key. If the entry exists, it will be touched so that it |
3317 | + /// becomes the most recently used item. |
3318 | + pub fn get_refresh(&mut self, key: &Oid) -> Option<u64> { |
3319 | + self.map.get_refresh(key).cloned() |
3320 | + } |
3321 | + |
3322 | + /// Adds an entry to the cache. Returns the previous value value of the |
3323 | + /// cache item if it already existed. Returns `None` if the entry did not |
3324 | + /// previously exist. In either case, the entry is always touched so that it |
3325 | + /// becomes the most recently used item. |
3326 | + pub fn push(&mut self, key: Oid, value: u64) -> Option<u64> { |
3327 | + self.size += value; |
3328 | + |
3329 | + if let Some(old_value) = self.map.insert(key, value) { |
3330 | + self.size -= old_value; |
3331 | + Some(old_value) |
3332 | + } else { |
3333 | + None |
3334 | + } |
3335 | + } |
3336 | + |
3337 | + /// Returns a future that prunes the least recently used entries that cause |
3338 | + /// the cache to exceed the given maximum size. |
3339 | + /// |
3340 | + /// If the stream is thrown away, then the items will not be deleted. |
3341 | + #[cfg(none)] |
3342 | + pub fn prune( |
3343 | + &mut self, |
3344 | + root: PathBuf, |
3345 | + max_size: u64, |
3346 | + ) -> impl Future<Item = usize, Error = io::Error> { |
3347 | + if max_size == 0 { |
3348 | + return Either::A(future::ok(0)); |
3349 | + } |
3350 | + |
3351 | + let mut to_delete = Vec::new(); |
3352 | + |
3353 | + while self.size() > max_size { |
3354 | + if let Some((oid, _)) = self.pop() { |
3355 | + to_delete.push(oid); |
3356 | + } |
3357 | + } |
3358 | + |
3359 | + Either::B(stream::iter_ok(to_delete).fold(0, move |acc, oid| { |
3360 | + let path = root.join(format!("objects/{}", oid.path())); |
3361 | + |
3362 | + fs::remove_file(path) |
3363 | + .map_err(move |e| { |
3364 | + log::error!("Failed to delete '{}' ({})", oid, e); |
3365 | + e |
3366 | + }) |
3367 | + .map(move |()| acc + 1) |
3368 | + })) |
3369 | + } |
3370 | + } |
3371 | diff --git a/src/main.rs b/src/main.rs |
3372 | new file mode 100644 |
3373 | index 0000000..f1b4754 |
3374 | --- /dev/null |
3375 | +++ b/src/main.rs |
3376 | @@ -0,0 +1,149 @@ |
3377 | + // Copyright (c) 2019 Jason White |
3378 | + // |
3379 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
3380 | + // of this software and associated documentation files (the "Software"), to deal |
3381 | + // in the Software without restriction, including without limitation the rights |
3382 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
3383 | + // copies of the Software, and to permit persons to whom the Software is |
3384 | + // furnished to do so, subject to the following conditions: |
3385 | + // |
3386 | + // The above copyright notice and this permission notice shall be included in |
3387 | + // all copies or substantial portions of the Software. |
3388 | + // |
3389 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
3390 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
3391 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
3392 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
3393 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
3394 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
3395 | + // SOFTWARE. |
3396 | + mod app; |
3397 | + mod error; |
3398 | + mod hyperext; |
3399 | + mod lfs; |
3400 | + mod logger; |
3401 | + mod lru; |
3402 | + mod sha256; |
3403 | + mod storage; |
3404 | + |
3405 | + use std::net::{SocketAddr, ToSocketAddrs}; |
3406 | + use std::path::PathBuf; |
3407 | + use std::process::exit; |
3408 | + use std::sync::Arc; |
3409 | + |
3410 | + use futures::Future; |
3411 | + use hex::FromHex; |
3412 | + use hyper::{self, server::conn::AddrStream, service::make_service_fn, Server}; |
3413 | + use log; |
3414 | + use pretty_env_logger; |
3415 | + use structopt::StructOpt; |
3416 | + |
3417 | + use crate::app::{App, State}; |
3418 | + use crate::error::Error; |
3419 | + use crate::logger::Logger; |
3420 | + use crate::storage::{Cached, Disk, Encrypted, Verify, S3}; |
3421 | + |
3422 | + #[derive(StructOpt)] |
3423 | + struct Args { |
3424 | + /// Host or address to listen on. |
3425 | + #[structopt(long = "host", default_value = "0.0.0.0:8080")] |
3426 | + host: String, |
3427 | + |
3428 | + /// Root directory of the object cache. |
3429 | + #[structopt(long = "cache-dir")] |
3430 | + cache_dir: PathBuf, |
3431 | + |
3432 | + /// Logging level to use. By default, uses `info`. |
3433 | + #[structopt(long = "log-level", default_value = "info")] |
3434 | + log_level: log::LevelFilter, |
3435 | + |
3436 | + /// Amazon S3 bucket to use. |
3437 | + #[structopt(long = "s3-bucket")] |
3438 | + s3_bucket: String, |
3439 | + |
3440 | + /// Encryption key to use. |
3441 | + #[structopt(long = "key", parse(try_from_str = "FromHex::from_hex"))] |
3442 | + key: [u8; 32], |
3443 | + |
3444 | + /// Maximum size of the cache, in bytes. Set to 0 for an unlimited cache |
3445 | + /// size. |
3446 | + #[structopt(long = "max-cache-size", default_value = "50 GiB")] |
3447 | + max_cache_size: human_size::Size, |
3448 | + } |
3449 | + |
3450 | + impl Args { |
3451 | + fn main(self) -> Result<(), Box<dyn std::error::Error>> { |
3452 | + // Initialize logging. |
3453 | + pretty_env_logger::formatted_timed_builder() |
3454 | + .filter_module("rudolfs", self.log_level) |
3455 | + .init(); |
3456 | + |
3457 | + // Find a socket address to bind to. This will resolve domain names. |
3458 | + let addr = self |
3459 | + .host |
3460 | + .to_socket_addrs()? |
3461 | + .next() |
3462 | + .unwrap_or_else(|| SocketAddr::from(([0, 0, 0, 0], 8080))); |
3463 | + |
3464 | + // Convert cache size to bytes. |
3465 | + let max_cache_size = |
3466 | + self.max_cache_size.into::<human_size::Byte>().value() as u64; |
3467 | + let key = self.key; |
3468 | + |
3469 | + let mut rt = tokio::runtime::Runtime::new()?; |
3470 | + |
3471 | + // Initialize our storage backends. |
3472 | + let disk = Disk::new(self.cache_dir).map_err(Error::from); |
3473 | + let s3 = S3::new(self.s3_bucket, "lfs".into()).map_err(Error::from); |
3474 | + let storage = disk |
3475 | + .join(s3) |
3476 | + .and_then(move |(disk, s3)| { |
3477 | + // Use the disk as a cache. |
3478 | + Cached::new(max_cache_size, disk, s3).from_err() |
3479 | + }) |
3480 | + .map(move |storage| { |
3481 | + // Verify object SHA256s as they are uploaded and downloaded. |
3482 | + Verify::new(Encrypted::new(key, storage)) |
3483 | + }); |
3484 | + |
3485 | + log::info!("Initializing storage..."); |
3486 | + let storage = rt.block_on(storage)?; |
3487 | + log::info!("Successfully initialized storage."); |
3488 | + |
3489 | + // Initialize the shared state. |
3490 | + let state = Arc::new(State::new(storage)); |
3491 | + |
3492 | + // Create our service factory. |
3493 | + let new_service = |
3494 | + make_service_fn(move |socket: &AddrStream| -> Result<_, Error> { |
3495 | + // Create our app. |
3496 | + let service = App::new(state.clone()); |
3497 | + |
3498 | + // Add logging middleware |
3499 | + let service = Logger::new(socket.remote_addr(), service); |
3500 | + |
3501 | + Ok(service) |
3502 | + }); |
3503 | + |
3504 | + // Create the server. |
3505 | + let server = Server::bind(&addr).serve(new_service); |
3506 | + |
3507 | + log::info!("Listening on {}", server.local_addr()); |
3508 | + |
3509 | + // Run the server. |
3510 | + rt.block_on_all(server)?; |
3511 | + |
3512 | + Ok(()) |
3513 | + } |
3514 | + } |
3515 | + |
3516 | + fn main() { |
3517 | + let exit_code = if let Err(err) = Args::from_args().main() { |
3518 | + log::error!("{}", err); |
3519 | + 1 |
3520 | + } else { |
3521 | + 0 |
3522 | + }; |
3523 | + |
3524 | + exit(exit_code); |
3525 | + } |
3526 | diff --git a/src/sha256.rs b/src/sha256.rs |
3527 | new file mode 100644 |
3528 | index 0000000..3572828 |
3529 | --- /dev/null |
3530 | +++ b/src/sha256.rs |
3531 | @@ -0,0 +1,341 @@ |
3532 | + // Copyright (c) 2019 Jason White |
3533 | + // |
3534 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
3535 | + // of this software and associated documentation files (the "Software"), to deal |
3536 | + // in the Software without restriction, including without limitation the rights |
3537 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
3538 | + // copies of the Software, and to permit persons to whom the Software is |
3539 | + // furnished to do so, subject to the following conditions: |
3540 | + // |
3541 | + // The above copyright notice and this permission notice shall be included in |
3542 | + // all copies or substantial portions of the Software. |
3543 | + // |
3544 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
3545 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
3546 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
3547 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
3548 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
3549 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
3550 | + // SOFTWARE. |
3551 | + use std::fmt; |
3552 | + use std::ops; |
3553 | + use std::str::FromStr; |
3554 | + |
3555 | + use futures::{try_ready, Async, Poll, Stream}; |
3556 | + use hex::{FromHex, FromHexError, ToHex}; |
3557 | + use serde::{ |
3558 | + de::{self, Deserializer, Visitor}, |
3559 | + ser::{self, Serializer}, |
3560 | + Deserialize, Serialize, |
3561 | + }; |
3562 | + |
3563 | + use generic_array::{typenum, GenericArray}; |
3564 | + use sha2::{self, Digest}; |
3565 | + |
3566 | + /// An error associated with parsing a SHA256. |
3567 | + #[derive(Debug, Clone, Copy, PartialEq)] |
3568 | + pub struct Sha256Error(FromHexError); |
3569 | + |
3570 | + impl From<FromHexError> for Sha256Error { |
3571 | + fn from(error: FromHexError) -> Self { |
3572 | + Sha256Error(error) |
3573 | + } |
3574 | + } |
3575 | + |
3576 | + impl fmt::Display for Sha256Error { |
3577 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3578 | + self.0.fmt(f) |
3579 | + } |
3580 | + } |
3581 | + |
3582 | + impl std::error::Error for Sha256Error {} |
3583 | + |
3584 | + /// A Git LFS object ID (i.e., a SHA256). |
3585 | + #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)] |
3586 | + pub struct Sha256(GenericArray<u8, typenum::U32>); |
3587 | + |
3588 | + impl Sha256 { |
3589 | + pub fn bytes(&self) -> &[u8] { |
3590 | + &self.0 |
3591 | + } |
3592 | + |
3593 | + /// Returns an object that can be formatted as a path. |
3594 | + pub fn path(&self) -> Sha256Path { |
3595 | + Sha256Path(self) |
3596 | + } |
3597 | + } |
3598 | + |
3599 | + pub struct Sha256Path<'a>(&'a Sha256); |
3600 | + |
3601 | + impl<'a> fmt::Display for Sha256Path<'a> { |
3602 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3603 | + write!( |
3604 | + f, |
3605 | + "{:02x}/{:02x}/{}", |
3606 | + self.0.bytes()[0], |
3607 | + self.0.bytes()[1], |
3608 | + self.0 |
3609 | + ) |
3610 | + } |
3611 | + } |
3612 | + |
3613 | + impl AsRef<[u8]> for Sha256 { |
3614 | + fn as_ref(&self) -> &[u8] { |
3615 | + &self.0 |
3616 | + } |
3617 | + } |
3618 | + |
3619 | + impl From<[u8; 32]> for Sha256 { |
3620 | + fn from(arr: [u8; 32]) -> Self { |
3621 | + Sha256(arr.into()) |
3622 | + } |
3623 | + } |
3624 | + |
3625 | + impl From<GenericArray<u8, typenum::U32>> for Sha256 { |
3626 | + fn from(arr: GenericArray<u8, typenum::U32>) -> Self { |
3627 | + Sha256(arr) |
3628 | + } |
3629 | + } |
3630 | + |
3631 | + impl FromHex for Sha256 { |
3632 | + type Error = Sha256Error; |
3633 | + |
3634 | + fn from_hex<T>(hex: T) -> Result<Self, Self::Error> |
3635 | + where |
3636 | + T: AsRef<[u8]>, |
3637 | + { |
3638 | + Ok(Sha256::from(<[u8; 32]>::from_hex(hex)?)) |
3639 | + } |
3640 | + } |
3641 | + |
3642 | + impl FromStr for Sha256 { |
3643 | + type Err = Sha256Error; |
3644 | + |
3645 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
3646 | + Self::from_hex(s) |
3647 | + } |
3648 | + } |
3649 | + |
3650 | + impl fmt::UpperHex for Sha256 { |
3651 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3652 | + self.write_hex_upper(f) |
3653 | + } |
3654 | + } |
3655 | + |
3656 | + impl fmt::LowerHex for Sha256 { |
3657 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3658 | + self.write_hex(f) |
3659 | + } |
3660 | + } |
3661 | + |
3662 | + impl fmt::Display for Sha256 { |
3663 | + #[inline] |
3664 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3665 | + <Self as fmt::LowerHex>::fmt(self, f) |
3666 | + } |
3667 | + } |
3668 | + |
3669 | + impl fmt::Debug for Sha256 { |
3670 | + #[inline] |
3671 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3672 | + <Self as fmt::Display>::fmt(self, f) |
3673 | + } |
3674 | + } |
3675 | + |
3676 | + impl ops::Deref for Sha256 { |
3677 | + type Target = [u8]; |
3678 | + |
3679 | + fn deref(&self) -> &Self::Target { |
3680 | + &self.0 |
3681 | + } |
3682 | + } |
3683 | + |
3684 | + impl Serialize for Sha256 { |
3685 | + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
3686 | + where |
3687 | + S: Serializer, |
3688 | + { |
3689 | + if serializer.is_human_readable() { |
3690 | + // Serialize as a hex string. |
3691 | + let mut hex = String::new(); |
3692 | + self.0 |
3693 | + .as_ref() |
3694 | + .write_hex(&mut hex) |
3695 | + .map_err(ser::Error::custom)?; |
3696 | + serializer.serialize_str(&hex) |
3697 | + } else { |
3698 | + // Serialize as a byte array with known length. |
3699 | + serializer.serialize_bytes(self.0.as_ref()) |
3700 | + } |
3701 | + } |
3702 | + } |
3703 | + |
3704 | + impl<'de> Deserialize<'de> for Sha256 { |
3705 | + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
3706 | + where |
3707 | + D: Deserializer<'de>, |
3708 | + { |
3709 | + struct Sha256Visitor; |
3710 | + |
3711 | + impl<'de> Visitor<'de> for Sha256Visitor { |
3712 | + type Value = Sha256; |
3713 | + |
3714 | + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3715 | + write!(f, "hex string or 32 bytes") |
3716 | + } |
3717 | + |
3718 | + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> |
3719 | + where |
3720 | + E: de::Error, |
3721 | + { |
3722 | + let v = Sha256::from_hex(v).map_err(|e| match e.0 { |
3723 | + FromHexError::InvalidHexCharacter { c, .. } => { |
3724 | + E::invalid_value( |
3725 | + de::Unexpected::Char(c), |
3726 | + &"string with only hexadecimal characters", |
3727 | + ) |
3728 | + } |
3729 | + FromHexError::InvalidStringLength => E::invalid_length( |
3730 | + v.len(), |
3731 | + &"hex string with a valid length", |
3732 | + ), |
3733 | + FromHexError::OddLength => E::invalid_length( |
3734 | + v.len(), |
3735 | + &"hex string with an even length", |
3736 | + ), |
3737 | + })?; |
3738 | + |
3739 | + Ok(v) |
3740 | + } |
3741 | + |
3742 | + fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> |
3743 | + where |
3744 | + E: de::Error, |
3745 | + { |
3746 | + if v.len() != 32 { |
3747 | + return Err(E::invalid_length(v.len(), &"32 bytes")); |
3748 | + } |
3749 | + |
3750 | + let mut inner = <[u8; 32]>::default(); |
3751 | + inner.copy_from_slice(v); |
3752 | + |
3753 | + Ok(Sha256::from(inner)) |
3754 | + } |
3755 | + } |
3756 | + |
3757 | + if deserializer.is_human_readable() { |
3758 | + deserializer.deserialize_str(Sha256Visitor) |
3759 | + } else { |
3760 | + deserializer.deserialize_bytes(Sha256Visitor) |
3761 | + } |
3762 | + } |
3763 | + } |
3764 | + |
3765 | + #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)] |
3766 | + pub struct Sha256VerifyError { |
3767 | + pub expected: Sha256, |
3768 | + pub found: Sha256, |
3769 | + } |
3770 | + |
3771 | + impl fmt::Display for Sha256VerifyError { |
3772 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3773 | + write!( |
3774 | + f, |
3775 | + "expected SHA256 '{}', but found '{}'", |
3776 | + self.expected, self.found |
3777 | + ) |
3778 | + } |
3779 | + } |
3780 | + |
3781 | + impl std::error::Error for Sha256VerifyError {} |
3782 | + |
3783 | + /// A stream adaptor that verifies the SHA256 of a byte stream. |
3784 | + pub struct VerifyStream<S> { |
3785 | + /// The underlying stream. |
3786 | + stream: S, |
3787 | + |
3788 | + /// The total size of the stream. |
3789 | + total: u64, |
3790 | + |
3791 | + /// The size so far. |
3792 | + len: u64, |
3793 | + |
3794 | + /// The expected SHA256. |
3795 | + expected: Sha256, |
3796 | + |
3797 | + /// The current state of the hasher. |
3798 | + hasher: sha2::Sha256, |
3799 | + } |
3800 | + |
3801 | + impl<S> VerifyStream<S> |
3802 | + where |
3803 | + S: Stream, |
3804 | + S::Item: AsRef<[u8]>, |
3805 | + S::Error: From<Sha256VerifyError>, |
3806 | + { |
3807 | + pub fn new(stream: S, total: u64, expected: Sha256) -> Self { |
3808 | + VerifyStream { |
3809 | + stream, |
3810 | + total, |
3811 | + len: 0, |
3812 | + expected, |
3813 | + hasher: sha2::Sha256::default(), |
3814 | + } |
3815 | + } |
3816 | + } |
3817 | + |
3818 | + impl<S> Stream for VerifyStream<S> |
3819 | + where |
3820 | + S: Stream, |
3821 | + S::Item: AsRef<[u8]>, |
3822 | + S::Error: From<Sha256VerifyError>, |
3823 | + { |
3824 | + type Item = S::Item; |
3825 | + type Error = S::Error; |
3826 | + |
3827 | + fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { |
3828 | + let bytes = try_ready!(self.stream.poll()); |
3829 | + |
3830 | + match bytes { |
3831 | + Some(bytes) => { |
3832 | + self.len += bytes.as_ref().len() as u64; |
3833 | + |
3834 | + // Continuously hash the bytes as we receive them. |
3835 | + self.hasher.input(bytes.as_ref()); |
3836 | + |
3837 | + if self.len >= self.total { |
3838 | + // This is the last chunk in the stream. Verify that the |
3839 | + // digest matches. |
3840 | + let found = Sha256::from(self.hasher.result_reset()); |
3841 | + |
3842 | + if found == self.expected { |
3843 | + Ok(Async::Ready(Some(bytes))) |
3844 | + } else { |
3845 | + Err(Self::Error::from(Sha256VerifyError { |
3846 | + found, |
3847 | + expected: self.expected, |
3848 | + })) |
3849 | + } |
3850 | + } else { |
3851 | + Ok(Async::Ready(Some(bytes))) |
3852 | + } |
3853 | + } |
3854 | + None => { |
3855 | + // End of stream. |
3856 | + Ok(Async::Ready(None)) |
3857 | + } |
3858 | + } |
3859 | + } |
3860 | + } |
3861 | + |
3862 | + #[cfg(test)] |
3863 | + mod tests { |
3864 | + use super::*; |
3865 | + |
3866 | + #[test] |
3867 | + fn sha256() { |
3868 | + let s = |
3869 | + "b1fbeefc23e6a149a6f7d0c2fb635bfc78f7ddc2da963ea9c6a63eb324260e6d"; |
3870 | + assert_eq!(Sha256::from_str(s).unwrap().to_string(), s); |
3871 | + } |
3872 | + } |
3873 | diff --git a/src/storage/cached.rs b/src/storage/cached.rs |
3874 | new file mode 100644 |
3875 | index 0000000..81012b6 |
3876 | --- /dev/null |
3877 | +++ b/src/storage/cached.rs |
3878 | @@ -0,0 +1,332 @@ |
3879 | + // Copyright (c) 2019 Jason White |
3880 | + // |
3881 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
3882 | + // of this software and associated documentation files (the "Software"), to deal |
3883 | + // in the Software without restriction, including without limitation the rights |
3884 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
3885 | + // copies of the Software, and to permit persons to whom the Software is |
3886 | + // furnished to do so, subject to the following conditions: |
3887 | + // |
3888 | + // The above copyright notice and this permission notice shall be included in |
3889 | + // all copies or substantial portions of the Software. |
3890 | + // |
3891 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
3892 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
3893 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
3894 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
3895 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
3896 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
3897 | + // SOFTWARE. |
3898 | + use std::fmt; |
3899 | + use std::sync::{Arc, Mutex}; |
3900 | + |
3901 | + use futures::{ |
3902 | + future::{self, Either}, |
3903 | + stream, Future, Stream, |
3904 | + }; |
3905 | + use tokio; |
3906 | + |
3907 | + use crate::lfs::Oid; |
3908 | + use crate::lru::Cache; |
3909 | + |
3910 | + use super::{LFSObject, Storage, StorageFuture, StorageStream}; |
3911 | + |
3912 | + #[derive(Debug)] |
3913 | + pub enum Error<C, S> { |
3914 | + /// An error that occurred in the cache. |
3915 | + Cache(C), |
3916 | + |
3917 | + /// An error that occurred in the storage backend. |
3918 | + Storage(S), |
3919 | + } |
3920 | + |
3921 | + impl<C, S> fmt::Display for Error<C, S> |
3922 | + where |
3923 | + C: fmt::Display, |
3924 | + S: fmt::Display, |
3925 | + { |
3926 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
3927 | + match self { |
3928 | + Error::Cache(x) => fmt::Display::fmt(&x, f), |
3929 | + Error::Storage(x) => fmt::Display::fmt(&x, f), |
3930 | + } |
3931 | + } |
3932 | + } |
3933 | + |
3934 | + impl<C, S> Error<C, S> { |
3935 | + pub fn from_cache(error: C) -> Self { |
3936 | + Error::Cache(error) |
3937 | + } |
3938 | + |
3939 | + pub fn from_storage(error: S) -> Self { |
3940 | + Error::Storage(error) |
3941 | + } |
3942 | + } |
3943 | + |
3944 | + impl<C, S> std::error::Error for Error<C, S> |
3945 | + where |
3946 | + C: fmt::Debug + fmt::Display, |
3947 | + S: fmt::Debug + fmt::Display, |
3948 | + { |
3949 | + } |
3950 | + |
3951 | + /// Combines a cache with a permanent storage backend such that if a query to |
3952 | + /// the cache fails, it falls back to a permanent storage backend. |
3953 | + pub struct Backend<C, S> { |
3954 | + lru: Arc<Mutex<Cache>>, |
3955 | + max_size: u64, |
3956 | + cache: Arc<C>, |
3957 | + storage: Arc<S>, |
3958 | + } |
3959 | + |
3960 | + impl<C, S> Backend<C, S> |
3961 | + where |
3962 | + C: Storage, |
3963 | + S: Storage, |
3964 | + { |
3965 | + pub fn new( |
3966 | + max_size: u64, |
3967 | + cache: C, |
3968 | + storage: S, |
3969 | + ) -> impl Future<Item = Self, Error = C::Error> { |
3970 | + Cache::from_stream(cache.list()).and_then(move |mut lru| { |
3971 | + let cache = Arc::new(cache); |
3972 | + |
3973 | + // Prune the cache. The maximum size setting may have changed |
3974 | + // between server invocations. Thus, prune it down right away |
3975 | + // instead of waiting for a client to do an upload. |
3976 | + prune_cache(&mut lru, max_size, cache.clone()).map(move |count| { |
3977 | + if count > 0 { |
3978 | + log::info!("Pruned {} entries from the cache", count); |
3979 | + } |
3980 | + |
3981 | + Backend { |
3982 | + lru: Arc::new(Mutex::new(lru)), |
3983 | + max_size, |
3984 | + cache, |
3985 | + storage: Arc::new(storage), |
3986 | + } |
3987 | + }) |
3988 | + }) |
3989 | + } |
3990 | + } |
3991 | + |
3992 | + /// Returns a future that prunes the least recently used entries that cause the |
3993 | + /// storage to exceed the given maximum size. |
3994 | + /// |
3995 | + /// If the stream is thrown away, then the items will be removed from the |
3996 | + /// in-memory LRU cache, but not physically deleted. |
3997 | + fn prune_cache<S>( |
3998 | + lru: &mut Cache, |
3999 | + max_size: u64, |
4000 | + storage: Arc<S>, |
4001 | + ) -> impl Future<Item = usize, Error = S::Error> |
4002 | + where |
4003 | + S: Storage, |
4004 | + { |
4005 | + if max_size == 0 { |
4006 | + // The cache can have unlimited size. |
4007 | + return Either::A(future::ok(0)); |
4008 | + } |
4009 | + |
4010 | + let mut to_delete = Vec::new(); |
4011 | + |
4012 | + while lru.size() > max_size { |
4013 | + if let Some((oid, _)) = lru.pop() { |
4014 | + to_delete.push(oid); |
4015 | + } |
4016 | + } |
4017 | + |
4018 | + Either::B(stream::iter_ok(to_delete).fold(0, move |acc, oid| { |
4019 | + storage.delete(&oid).map(move |()| acc + 1) |
4020 | + })) |
4021 | + } |
4022 | + |
4023 | + fn cache_and_prune<C>( |
4024 | + cache: Arc<C>, |
4025 | + key: Oid, |
4026 | + obj: LFSObject, |
4027 | + lru: Arc<Mutex<Cache>>, |
4028 | + max_size: u64, |
4029 | + ) -> impl Future<Item = (), Error = ()> |
4030 | + where |
4031 | + C: Storage, |
4032 | + { |
4033 | + let len = obj.len(); |
4034 | + |
4035 | + cache |
4036 | + .put(&key, obj) |
4037 | + .and_then(move |()| { |
4038 | + // Add the object info to our LRU cache once the download from |
4039 | + // permanent storage is complete. |
4040 | + let mut lru = lru.lock().unwrap(); |
4041 | + lru.push(key, len); |
4042 | + |
4043 | + // Prune the cache. |
4044 | + prune_cache(&mut lru, max_size, cache).map(move |count| { |
4045 | + if count > 0 { |
4046 | + log::info!("Pruned {} entries from the cache", count); |
4047 | + } |
4048 | + }) |
4049 | + }) |
4050 | + .map_err(move |err| { |
4051 | + log::error!("Error caching {} ({})", key, err); |
4052 | + }) |
4053 | + } |
4054 | + |
4055 | + impl<C, S> Storage for Backend<C, S> |
4056 | + where |
4057 | + S: Storage + Send + Sync + 'static, |
4058 | + S::Error: 'static, |
4059 | + C: Storage + Send + Sync + 'static, |
4060 | + C::Error: 'static, |
4061 | + { |
4062 | + type Error = Error<C::Error, S::Error>; |
4063 | + |
4064 | + /// Tries to query the cache first. If that fails, falls back to the |
4065 | + /// permanent storage backend. |
4066 | + fn get(&self, key: &Oid) -> StorageFuture<Option<LFSObject>, Self::Error> { |
4067 | + // TODO: Keep stats on cache hits and misses. We can then display those |
4068 | + // stats on a web page or send them to another service such as |
4069 | + // Prometheus. |
4070 | + let key = *key; |
4071 | + |
4072 | + if let Ok(mut lru) = self.lru.lock() { |
4073 | + if lru.get_refresh(&key).is_some() { |
4074 | + // Cache hit! |
4075 | + // |
4076 | + // TODO: Verify the stream as we send it back. If the SHA256 is |
4077 | + // incorrect, delete it and let the client try again. |
4078 | + let storage = self.storage.clone(); |
4079 | + let lru2 = self.lru.clone(); |
4080 | + |
4081 | + return Box::new( |
4082 | + self.cache.get(&key).map_err(Error::from_cache).and_then( |
4083 | + move |obj| match obj { |
4084 | + Some(obj) => Either::A(future::ok(Some(obj))), |
4085 | + None => { |
4086 | + // If the cache doesn't actually have it, delete |
4087 | + // the entry from our LRU. This can happen if |
4088 | + // the cache is cleared out manually. |
4089 | + let mut lru = lru2.lock().unwrap(); |
4090 | + lru.remove(&key); |
4091 | + |
4092 | + // Fall back to permanent storage. Note that |
4093 | + // this won't actually cache the object. This |
4094 | + // will be done next time the same object is |
4095 | + // requested. |
4096 | + Either::B( |
4097 | + storage |
4098 | + .get(&key) |
4099 | + .map_err(Error::from_storage), |
4100 | + ) |
4101 | + } |
4102 | + }, |
4103 | + ), |
4104 | + ); |
4105 | + } |
4106 | + } |
4107 | + |
4108 | + // Cache miss. Get the object from permanent storage. If successful, we |
4109 | + // to cache the resulting byte stream. |
4110 | + let lru = self.lru.clone(); |
4111 | + let max_size = self.max_size; |
4112 | + let cache = self.cache.clone(); |
4113 | + |
4114 | + Box::new( |
4115 | + self.storage |
4116 | + .get(&key) |
4117 | + .map_err(Error::from_storage) |
4118 | + .and_then(move |obj| match obj { |
4119 | + Some(obj) => { |
4120 | + // Cache the returned LFS object. |
4121 | + let (a, b) = obj.split(); |
4122 | + |
4123 | + // Cache the object in the background. Whether or not |
4124 | + // this succeeds shouldn't prevent the client from |
4125 | + // getting the LFS object. For example, even if we run |
4126 | + // out of disk space, the server should still continue |
4127 | + // operating. |
4128 | + tokio::spawn(cache_and_prune( |
4129 | + cache, key, b, lru, max_size, |
4130 | + )); |
4131 | + |
4132 | + // Send the object from permanent-storage. |
4133 | + Either::A(future::ok(Some(a))) |
4134 | + } |
4135 | + None => { |
4136 | + // The permanent storage also doesn't have it. |
4137 | + // |
4138 | + // Note that we cannot cache the non-existence of an |
4139 | + // object because the storage backend might be shared by |
4140 | + // multiple caches. |
4141 | + Either::B(future::ok(None)) |
4142 | + } |
4143 | + }), |
4144 | + ) |
4145 | + } |
4146 | + |
4147 | + fn put( |
4148 | + &self, |
4149 | + key: &Oid, |
4150 | + value: LFSObject, |
4151 | + ) -> StorageFuture<(), Self::Error> { |
4152 | + let key = *key; |
4153 | + let lru = self.lru.clone(); |
4154 | + let max_size = self.max_size; |
4155 | + let cache = self.cache.clone(); |
4156 | + |
4157 | + let (a, b) = value.split(); |
4158 | + |
4159 | + // Cache the object in the background. Whether or not this succeeds |
4160 | + // shouldn't prevent the client from uploading the LFS object to |
4161 | + // permanent storage. For example, even if we run out of disk space, the |
4162 | + // server should still continue operating. |
4163 | + tokio::spawn(cache_and_prune(cache, key, b, lru, max_size)); |
4164 | + |
4165 | + Box::new(self.storage.put(&key, a).map_err(Error::from_storage)) |
4166 | + } |
4167 | + |
4168 | + fn size(&self, key: &Oid) -> StorageFuture<Option<u64>, Self::Error> { |
4169 | + // Get just the size of an object without perturbing the LRU ordering. |
4170 | + // Only downloads or uploads need to perturb the LRU ordering. |
4171 | + let lru = self.lru.lock().unwrap(); |
4172 | + if let Some(size) = lru.get(key) { |
4173 | + // Cache hit! |
4174 | + Box::new(future::ok(Some(size))) |
4175 | + } else { |
4176 | + // Cache miss. Check permanent storage. |
4177 | + Box::new(self.storage.size(key).map_err(Error::from_storage)) |
4178 | + } |
4179 | + } |
4180 | + |
4181 | + /// Deletes an item from the cache (not from permanent storage). |
4182 | + fn delete(&self, key: &Oid) -> StorageFuture<(), Self::Error> { |
4183 | + // Only ever delete items from the cache. This may be called when |
4184 | + // a corrupted object is detected. |
4185 | + Box::new(self.cache.delete(key).map_err(Error::from_cache)) |
4186 | + } |
4187 | + |
4188 | + /// Returns a stream of cached items. |
4189 | + fn list(&self) -> StorageStream<(Oid, u64), Self::Error> { |
4190 | + // TODO: Use the internal linked hash map instead to get this list. |
4191 | + Box::new(self.cache.list().map_err(Error::from_cache)) |
4192 | + } |
4193 | + |
4194 | + /// Returns the total size of the LRU cache (not the total size of the |
4195 | + /// permanent storage). |
4196 | + fn total_size(&self) -> Option<u64> { |
4197 | + let lru = self.lru.lock().unwrap(); |
4198 | + Some(lru.size()) |
4199 | + } |
4200 | + |
4201 | + /// Returns the maximum size of the LRU cache (not the maximum size of the |
4202 | + /// permanent storage). |
4203 | + fn max_size(&self) -> Option<u64> { |
4204 | + if self.max_size == 0 { |
4205 | + None |
4206 | + } else { |
4207 | + Some(self.max_size) |
4208 | + } |
4209 | + } |
4210 | + } |
4211 | diff --git a/src/storage/disk.rs b/src/storage/disk.rs |
4212 | new file mode 100644 |
4213 | index 0000000..7a7d47f |
4214 | --- /dev/null |
4215 | +++ b/src/storage/disk.rs |
4216 | @@ -0,0 +1,177 @@ |
4217 | + // Copyright (c) 2019 Jason White |
4218 | + // |
4219 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
4220 | + // of this software and associated documentation files (the "Software"), to deal |
4221 | + // in the Software without restriction, including without limitation the rights |
4222 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
4223 | + // copies of the Software, and to permit persons to whom the Software is |
4224 | + // furnished to do so, subject to the following conditions: |
4225 | + // |
4226 | + // The above copyright notice and this permission notice shall be included in |
4227 | + // all copies or substantial portions of the Software. |
4228 | + // |
4229 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
4230 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
4231 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
4232 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
4233 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
4234 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
4235 | + // SOFTWARE. |
4236 | + use std::ffi::OsStr; |
4237 | + use std::io; |
4238 | + use std::path::PathBuf; |
4239 | + use std::str::FromStr; |
4240 | + |
4241 | + use bytes::BytesMut; |
4242 | + use futures::{future, Future, Stream}; |
4243 | + use tokio::{ |
4244 | + self, |
4245 | + codec::{BytesCodec, Framed}, |
4246 | + fs, |
4247 | + }; |
4248 | + use uuid::Uuid; |
4249 | + |
4250 | + use super::{LFSObject, Storage, StorageFuture, StorageStream}; |
4251 | + use crate::lfs::Oid; |
4252 | + |
4253 | + pub struct Backend { |
4254 | + root: PathBuf, |
4255 | + } |
4256 | + |
4257 | + impl Backend { |
4258 | + pub fn new(root: PathBuf) -> impl Future<Item = Self, Error = io::Error> { |
4259 | + // TODO: Clean out files in the "incomplete" folder. |
4260 | + future::ok(Backend { root }) |
4261 | + } |
4262 | + |
4263 | + // Use sub directories in order to better utilize the file system's internal |
4264 | + // tree data structure. |
4265 | + fn key_to_path(&self, oid: &Oid) -> PathBuf { |
4266 | + self.root.join(format!("objects/{}", oid.path())) |
4267 | + } |
4268 | + } |
4269 | + |
4270 | + impl Storage for Backend { |
4271 | + type Error = io::Error; |
4272 | + |
4273 | + fn get(&self, key: &Oid) -> StorageFuture<Option<LFSObject>, Self::Error> { |
4274 | + Box::new( |
4275 | + fs::File::open(self.key_to_path(key)) |
4276 | + .and_then(fs::File::metadata) |
4277 | + .then(move |result| { |
4278 | + Ok(match result { |
4279 | + Ok((file, metadata)) => { |
4280 | + let stream = Framed::new(file, BytesCodec::new()) |
4281 | + .map(BytesMut::freeze); |
4282 | + |
4283 | + Some(LFSObject::new( |
4284 | + metadata.len(), |
4285 | + Box::new(stream), |
4286 | + )) |
4287 | + } |
4288 | + Err(err) => match err.kind() { |
4289 | + io::ErrorKind::NotFound => None, |
4290 | + _ => return Err(err), |
4291 | + }, |
4292 | + }) |
4293 | + }), |
4294 | + ) |
4295 | + } |
4296 | + |
4297 | + fn put( |
4298 | + &self, |
4299 | + key: &Oid, |
4300 | + value: LFSObject, |
4301 | + ) -> StorageFuture<(), Self::Error> { |
4302 | + let path = self.key_to_path(key); |
4303 | + let dir = path.parent().unwrap().to_path_buf(); |
4304 | + |
4305 | + let incomplete = self.root.join("incomplete"); |
4306 | + let temp_path = incomplete.join(Uuid::new_v4().to_string()); |
4307 | + let temp_path2 = temp_path.clone(); |
4308 | + |
4309 | + Box::new( |
4310 | + fs::create_dir_all(incomplete) |
4311 | + .and_then(move |()| fs::File::create(temp_path)) |
4312 | + .and_then(move |file| { |
4313 | + value.stream().forward(Framed::new(file, BytesCodec::new())) |
4314 | + }) |
4315 | + .and_then(move |_| fs::create_dir_all(dir)) |
4316 | + .and_then(move |()| fs::rename(temp_path2, path)), |
4317 | + ) |
4318 | + } |
4319 | + |
4320 | + fn size(&self, key: &Oid) -> StorageFuture<Option<u64>, Self::Error> { |
4321 | + let path = self.key_to_path(key); |
4322 | + |
4323 | + Box::new( |
4324 | + fs::metadata(path) |
4325 | + .map(move |metadata| Some(metadata.len())) |
4326 | + .or_else(move |err| match err.kind() { |
4327 | + io::ErrorKind::NotFound => Ok(None), |
4328 | + _ => Err(err), |
4329 | + }), |
4330 | + ) |
4331 | + } |
4332 | + |
4333 | + fn delete(&self, key: &Oid) -> StorageFuture<(), Self::Error> { |
4334 | + Box::new(fs::remove_file(self.key_to_path(key)).or_else(move |err| { |
4335 | + match err.kind() { |
4336 | + io::ErrorKind::NotFound => Ok(()), |
4337 | + _ => Err(err), |
4338 | + } |
4339 | + })) |
4340 | + } |
4341 | + |
4342 | + /// Lists the objects that are on disk. |
4343 | + /// |
4344 | + /// The directory structure is assumed to be like this: |
4345 | + /// |
4346 | + /// objects |
4347 | + /// ├── 00 |
4348 | + /// │ ├── 07 |
4349 | + /// │ │ └── 0007941906960... |
4350 | + /// │ └── ff |
4351 | + /// │ └── 00ff9e9c69224... |
4352 | + /// ├── 01 |
4353 | + /// │ ├── 89 |
4354 | + /// │ │ └── 0189e5fd19477... |
4355 | + /// │ └── f5 |
4356 | + /// │ └── 01f5c45c65e62... |
4357 | + /// ^^^^ |
4358 | + /// |
4359 | + /// Note that the first four characters are repeated in the file name so |
4360 | + /// that transforming the file name into an object ID is simpler. |
4361 | + fn list(&self) -> StorageStream<(Oid, u64), Self::Error> { |
4362 | + let path = self.root.join("objects"); |
4363 | + |
4364 | + Box::new( |
4365 | + fs::read_dir(path) |
4366 | + .flatten_stream() |
4367 | + .map(move |entry| fs::read_dir(entry.path()).flatten_stream()) |
4368 | + .flatten() |
4369 | + .map(move |entry| fs::read_dir(entry.path()).flatten_stream()) |
4370 | + .flatten() |
4371 | + .and_then(move |entry| { |
4372 | + let path = entry.path(); |
4373 | + future::poll_fn(move || entry.poll_metadata()) |
4374 | + .map(move |metadata| (path, metadata)) |
4375 | + }) |
4376 | + .filter_map(move |(path, metadata)| { |
4377 | + if let Some(oid) = path |
4378 | + .file_name() |
4379 | + .and_then(OsStr::to_str) |
4380 | + .and_then(|s| Oid::from_str(s).ok()) |
4381 | + { |
4382 | + if metadata.is_file() { |
4383 | + Some((oid, metadata.len())) |
4384 | + } else { |
4385 | + None |
4386 | + } |
4387 | + } else { |
4388 | + None |
4389 | + } |
4390 | + }), |
4391 | + ) |
4392 | + } |
4393 | + } |
4394 | diff --git a/src/storage/encrypt.rs b/src/storage/encrypt.rs |
4395 | new file mode 100644 |
4396 | index 0000000..2e7394f |
4397 | --- /dev/null |
4398 | +++ b/src/storage/encrypt.rs |
4399 | @@ -0,0 +1,125 @@ |
4400 | + // Copyright (c) 2019 Jason White |
4401 | + // |
4402 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
4403 | + // of this software and associated documentation files (the "Software"), to deal |
4404 | + // in the Software without restriction, including without limitation the rights |
4405 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
4406 | + // copies of the Software, and to permit persons to whom the Software is |
4407 | + // furnished to do so, subject to the following conditions: |
4408 | + // |
4409 | + // The above copyright notice and this permission notice shall be included in |
4410 | + // all copies or substantial portions of the Software. |
4411 | + // |
4412 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
4413 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
4414 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
4415 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
4416 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
4417 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
4418 | + // SOFTWARE. |
4419 | + use chacha::{ChaCha, KeyStream}; |
4420 | + use std::io; |
4421 | + |
4422 | + use bytes::{Bytes, BytesMut}; |
4423 | + use futures::{Future, Stream}; |
4424 | + |
4425 | + use crate::lfs::Oid; |
4426 | + |
4427 | + use super::{LFSObject, Storage, StorageFuture, StorageStream}; |
4428 | + |
4429 | + /// A storage adaptor that encrypts/decrypts all data that passes through. |
4430 | + pub struct Backend<S> { |
4431 | + storage: S, |
4432 | + key: [u8; 32], |
4433 | + } |
4434 | + |
4435 | + impl<S> Backend<S> { |
4436 | + pub fn new(key: [u8; 32], storage: S) -> Self { |
4437 | + Backend { key, storage } |
4438 | + } |
4439 | + } |
4440 | + |
4441 | + fn xor_stream<S>( |
4442 | + mut chacha: ChaCha, |
4443 | + stream: S, |
4444 | + ) -> impl Stream<Item = Bytes, Error = io::Error> |
4445 | + where |
4446 | + S: Stream<Item = Bytes, Error = io::Error>, |
4447 | + { |
4448 | + stream.and_then(move |bytes| { |
4449 | + let mut bytes = BytesMut::from(bytes); |
4450 | + |
4451 | + chacha.xor_read(bytes.as_mut()).map_err(|_| { |
4452 | + io::Error::new( |
4453 | + io::ErrorKind::Other, |
4454 | + "reached end of xchacha20 keystream", |
4455 | + ) |
4456 | + })?; |
4457 | + |
4458 | + Ok(bytes.freeze()) |
4459 | + }) |
4460 | + } |
4461 | + |
4462 | + impl<S> Storage for Backend<S> |
4463 | + where |
4464 | + S: Storage + Send + Sync + 'static, |
4465 | + S::Error: 'static, |
4466 | + { |
4467 | + type Error = S::Error; |
4468 | + |
4469 | + fn get(&self, key: &Oid) -> StorageFuture<Option<LFSObject>, Self::Error> { |
4470 | + // Use the first part of the SHA256 as the nonce. |
4471 | + let mut nonce: [u8; 24] = [0; 24]; |
4472 | + nonce.copy_from_slice(&key.bytes()[0..24]); |
4473 | + |
4474 | + let chacha = ChaCha::new_xchacha20(&self.key, &nonce); |
4475 | + |
4476 | + Box::new(self.storage.get(key).and_then(move |obj| match obj { |
4477 | + Some(obj) => { |
4478 | + let (len, stream) = obj.into_parts(); |
4479 | + Ok(Some(LFSObject::new( |
4480 | + len, |
4481 | + Box::new(xor_stream(chacha, stream)), |
4482 | + ))) |
4483 | + } |
4484 | + None => Ok(None), |
4485 | + })) |
4486 | + } |
4487 | + |
4488 | + fn put( |
4489 | + &self, |
4490 | + key: &Oid, |
4491 | + value: LFSObject, |
4492 | + ) -> StorageFuture<(), Self::Error> { |
4493 | + // Use the first part of the SHA256 as the nonce. |
4494 | + let mut nonce: [u8; 24] = [0; 24]; |
4495 | + nonce.copy_from_slice(&key.bytes()[0..24]); |
4496 | + |
4497 | + let chacha = ChaCha::new_xchacha20(&self.key, &nonce); |
4498 | + |
4499 | + let (len, stream) = value.into_parts(); |
4500 | + let stream = xor_stream(chacha, stream); |
4501 | + |
4502 | + self.storage.put(key, LFSObject::new(len, Box::new(stream))) |
4503 | + } |
4504 | + |
4505 | + fn size(&self, key: &Oid) -> StorageFuture<Option<u64>, Self::Error> { |
4506 | + self.storage.size(key) |
4507 | + } |
4508 | + |
4509 | + fn delete(&self, key: &Oid) -> StorageFuture<(), Self::Error> { |
4510 | + self.storage.delete(key) |
4511 | + } |
4512 | + |
4513 | + fn list(&self) -> StorageStream<(Oid, u64), Self::Error> { |
4514 | + self.storage.list() |
4515 | + } |
4516 | + |
4517 | + fn total_size(&self) -> Option<u64> { |
4518 | + self.storage.total_size() |
4519 | + } |
4520 | + |
4521 | + fn max_size(&self) -> Option<u64> { |
4522 | + self.storage.max_size() |
4523 | + } |
4524 | + } |
4525 | diff --git a/src/storage/mod.rs b/src/storage/mod.rs |
4526 | new file mode 100644 |
4527 | index 0000000..2e5407a |
4528 | --- /dev/null |
4529 | +++ b/src/storage/mod.rs |
4530 | @@ -0,0 +1,193 @@ |
4531 | + // Copyright (c) 2019 Jason White |
4532 | + // |
4533 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
4534 | + // of this software and associated documentation files (the "Software"), to deal |
4535 | + // in the Software without restriction, including without limitation the rights |
4536 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
4537 | + // copies of the Software, and to permit persons to whom the Software is |
4538 | + // furnished to do so, subject to the following conditions: |
4539 | + // |
4540 | + // The above copyright notice and this permission notice shall be included in |
4541 | + // all copies or substantial portions of the Software. |
4542 | + // |
4543 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
4544 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
4545 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
4546 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
4547 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
4548 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
4549 | + // SOFTWARE. |
4550 | + mod cached; |
4551 | + mod disk; |
4552 | + mod encrypt; |
4553 | + mod s3; |
4554 | + mod verify; |
4555 | + |
4556 | + pub use cached::{Backend as Cached, Error as CacheError}; |
4557 | + pub use disk::Backend as Disk; |
4558 | + pub use encrypt::Backend as Encrypted; |
4559 | + pub use s3::{Backend as S3, Error as S3Error}; |
4560 | + pub use verify::Backend as Verify; |
4561 | + |
4562 | + use std::fmt; |
4563 | + |
4564 | + use bytes::Bytes; |
4565 | + use std::io; |
4566 | + |
4567 | + use crate::lfs::Oid; |
4568 | + use futures::{sync::mpsc, Future, Sink, Stream}; |
4569 | + |
4570 | + pub type S3DiskCache = Cached<Disk, S3>; |
4571 | + |
4572 | + /// Future returned by storage operations. |
4573 | + pub type StorageFuture<T, E> = Box<dyn Future<Item = T, Error = E> + Send>; |
4574 | + |
4575 | + /// Stream returned by storage operations. |
4576 | + pub type StorageStream<T, E> = Box<dyn Stream<Item = T, Error = E> + Send>; |
4577 | + |
4578 | + /// The byte stream of an LFS object. |
4579 | + pub type ByteStream = Box<dyn Stream<Item = Bytes, Error = io::Error> + Send>; |
4580 | + |
4581 | + /// An LFS object to be uploaded or downloaded. |
4582 | + pub struct LFSObject { |
4583 | + /// Size of the object. |
4584 | + len: u64, |
4585 | + |
4586 | + /// The stream of bytes. |
4587 | + stream: ByteStream, |
4588 | + } |
4589 | + |
4590 | + impl LFSObject { |
4591 | + pub fn new(len: u64, stream: ByteStream) -> Self { |
4592 | + LFSObject { len, stream } |
4593 | + } |
4594 | + |
4595 | + pub fn len(&self) -> u64 { |
4596 | + self.len |
4597 | + } |
4598 | + |
4599 | + pub fn stream(self) -> ByteStream { |
4600 | + self.stream |
4601 | + } |
4602 | + |
4603 | + pub fn into_parts(self) -> (u64, ByteStream) { |
4604 | + (self.len, self.stream) |
4605 | + } |
4606 | + |
4607 | + /// Duplicates the underlying byte stream such that we have two identical |
4608 | + /// LFS object streams that must be consumed in lock-step. |
4609 | + /// |
4610 | + /// This is useful for caching LFS objects while simultaneously sending them |
4611 | + /// to a client. |
4612 | + pub fn split(self) -> (Self, Self) { |
4613 | + let (len, stream) = self.into_parts(); |
4614 | + |
4615 | + let (sender, receiver) = mpsc::channel(1); |
4616 | + |
4617 | + let stream = stream.and_then(move |chunk| { |
4618 | + // TODO: Find a way to not clone the sender. |
4619 | + sender |
4620 | + .clone() |
4621 | + .send(chunk.clone()) |
4622 | + .and_then(move |_| Ok(chunk)) |
4623 | + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) |
4624 | + }); |
4625 | + |
4626 | + let a = LFSObject { |
4627 | + len, |
4628 | + stream: Box::new(stream), |
4629 | + }; |
4630 | + |
4631 | + let b = LFSObject { |
4632 | + len, |
4633 | + stream: Box::new(receiver.map_err(|()| { |
4634 | + io::Error::new( |
4635 | + io::ErrorKind::Other, |
4636 | + "failed during body duplication", |
4637 | + ) |
4638 | + })), |
4639 | + }; |
4640 | + |
4641 | + (a, b) |
4642 | + } |
4643 | + } |
4644 | + |
4645 | + /// Trait for abstracting away the storage medium. |
4646 | + pub trait Storage { |
4647 | + type Error: fmt::Display + Send; |
4648 | + |
4649 | + /// Gets an entry from the storage medium. |
4650 | + fn get(&self, key: &Oid) -> StorageFuture<Option<LFSObject>, Self::Error>; |
4651 | + |
4652 | + /// Sets an entry in the storage medium. |
4653 | + fn put( |
4654 | + &self, |
4655 | + key: &Oid, |
4656 | + value: LFSObject, |
4657 | + ) -> StorageFuture<(), Self::Error>; |
4658 | + |
4659 | + /// Gets the size of the object. Returns `None` if the object does not |
4660 | + /// exist. |
4661 | + fn size(&self, key: &Oid) -> StorageFuture<Option<u64>, Self::Error>; |
4662 | + |
4663 | + /// Deletes an object. |
4664 | + /// |
4665 | + /// Permanent storage backends may choose to never delete objects, always |
4666 | + /// returning success. |
4667 | + fn delete(&self, key: &Oid) -> StorageFuture<(), Self::Error>; |
4668 | + |
4669 | + /// Returns a stream of all the object IDs in the storage medium. |
4670 | + fn list(&self) -> StorageStream<(Oid, u64), Self::Error>; |
4671 | + |
4672 | + /// Gets the total size of the storage, if known. |
4673 | + fn total_size(&self) -> Option<u64> { |
4674 | + None |
4675 | + } |
4676 | + |
4677 | + /// Gets the maximum size of the storage. |
4678 | + /// |
4679 | + /// This should return `None` if the storage size is unbounded. This is only |
4680 | + /// applicable to caches. |
4681 | + fn max_size(&self) -> Option<u64> { |
4682 | + None |
4683 | + } |
4684 | + } |
4685 | + |
4686 | + impl<S> Storage for Box<S> |
4687 | + where |
4688 | + S: Storage + ?Sized, |
4689 | + { |
4690 | + type Error = S::Error; |
4691 | + |
4692 | + fn get(&self, key: &Oid) -> StorageFuture<Option<LFSObject>, Self::Error> { |
4693 | + (**self).get(key) |
4694 | + } |
4695 | + |
4696 | + fn put( |
4697 | + &self, |
4698 | + key: &Oid, |
4699 | + value: LFSObject, |
4700 | + ) -> StorageFuture<(), Self::Error> { |
4701 | + (**self).put(key, value) |
4702 | + } |
4703 | + |
4704 | + fn size(&self, key: &Oid) -> StorageFuture<Option<u64>, Self::Error> { |
4705 | + (**self).size(key) |
4706 | + } |
4707 | + |
4708 | + fn delete(&self, key: &Oid) -> StorageFuture<(), Self::Error> { |
4709 | + (**self).delete(key) |
4710 | + } |
4711 | + |
4712 | + fn list(&self) -> StorageStream<(Oid, u64), Self::Error> { |
4713 | + (**self).list() |
4714 | + } |
4715 | + |
4716 | + fn total_size(&self) -> Option<u64> { |
4717 | + (**self).total_size() |
4718 | + } |
4719 | + |
4720 | + fn max_size(&self) -> Option<u64> { |
4721 | + (**self).max_size() |
4722 | + } |
4723 | + } |
4724 | diff --git a/src/storage/s3.rs b/src/storage/s3.rs |
4725 | new file mode 100644 |
4726 | index 0000000..def688c |
4727 | --- /dev/null |
4728 | +++ b/src/storage/s3.rs |
4729 | @@ -0,0 +1,240 @@ |
4730 | + // Copyright (c) 2019 Jason White |
4731 | + // |
4732 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
4733 | + // of this software and associated documentation files (the "Software"), to deal |
4734 | + // in the Software without restriction, including without limitation the rights |
4735 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
4736 | + // copies of the Software, and to permit persons to whom the Software is |
4737 | + // furnished to do so, subject to the following conditions: |
4738 | + // |
4739 | + // The above copyright notice and this permission notice shall be included in |
4740 | + // all copies or substantial portions of the Software. |
4741 | + // |
4742 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
4743 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
4744 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
4745 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
4746 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
4747 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
4748 | + // SOFTWARE. |
4749 | + use bytes::Bytes; |
4750 | + use derive_more::{Display, From}; |
4751 | + use futures::{future, stream, Future, Stream}; |
4752 | + use http::StatusCode; |
4753 | + use rusoto_core::Region; |
4754 | + use rusoto_s3::{ |
4755 | + GetObjectError, GetObjectRequest, HeadBucketError, HeadBucketRequest, |
4756 | + HeadObjectError, HeadObjectRequest, PutObjectError, PutObjectRequest, |
4757 | + S3Client, StreamingBody, S3, |
4758 | + }; |
4759 | + |
4760 | + use super::{LFSObject, Storage, StorageFuture, StorageStream}; |
4761 | + |
4762 | + use crate::lfs::Oid; |
4763 | + |
4764 | + #[derive(Debug, From, Display)] |
4765 | + pub enum Error { |
4766 | + GetObjectError(GetObjectError), |
4767 | + PutObjectError(PutObjectError), |
4768 | + HeadObjectError(HeadObjectError), |
4769 | + |
4770 | + /// Initialization error. |
4771 | + Init(InitError), |
4772 | + |
4773 | + /// The uploaded object is too large. |
4774 | + TooLarge(u64), |
4775 | + } |
4776 | + |
4777 | + impl ::std::error::Error for Error {} |
4778 | + |
4779 | + #[derive(Debug, Display)] |
4780 | + pub enum InitError { |
4781 | + #[display(fmt = "Invalid S3 bucket name")] |
4782 | + Bucket, |
4783 | + |
4784 | + #[display(fmt = "Invalid S3 credentials")] |
4785 | + Credentials, |
4786 | + |
4787 | + #[display(fmt = "{}", _0)] |
4788 | + Other(HeadBucketError), |
4789 | + } |
4790 | + |
4791 | + impl From<HeadBucketError> for InitError { |
4792 | + fn from(err: HeadBucketError) -> Self { |
4793 | + match err { |
4794 | + HeadBucketError::Unknown(r) => { |
4795 | + // Rusoto really sucks at correctly reporting errors. |
4796 | + // Lets work around that here. |
4797 | + match r.status { |
4798 | + StatusCode::NOT_FOUND => InitError::Bucket, |
4799 | + StatusCode::FORBIDDEN => InitError::Credentials, |
4800 | + _ => InitError::Other(HeadBucketError::Unknown(r)), |
4801 | + } |
4802 | + } |
4803 | + x => InitError::Other(x), |
4804 | + } |
4805 | + } |
4806 | + } |
4807 | + |
4808 | + /// Amazon S3 storage backend. |
4809 | + pub struct Backend<C = S3Client> { |
4810 | + /// S3 client. |
4811 | + client: C, |
4812 | + |
4813 | + /// Name of the bucket to use. |
4814 | + bucket: String, |
4815 | + |
4816 | + /// Prefix for objects. |
4817 | + prefix: String, |
4818 | + } |
4819 | + |
4820 | + impl Backend { |
4821 | + pub fn new( |
4822 | + bucket: String, |
4823 | + prefix: String, |
4824 | + ) -> impl Future<Item = Self, Error = Error> { |
4825 | + Backend::with_client(S3Client::new(Region::default()), bucket, prefix) |
4826 | + } |
4827 | + } |
4828 | + |
4829 | + impl<C> Backend<C> |
4830 | + where |
4831 | + C: S3, |
4832 | + { |
4833 | + pub fn with_client( |
4834 | + client: C, |
4835 | + bucket: String, |
4836 | + prefix: String, |
4837 | + ) -> impl Future<Item = Self, Error = Error> { |
4838 | + // Peform a HEAD operation to check that the bucket exists and that our |
4839 | + // credentials work. This helps catch very common errors early on |
4840 | + // application startup. |
4841 | + let req = HeadBucketRequest { |
4842 | + bucket: bucket.clone(), |
4843 | + }; |
4844 | + |
4845 | + client |
4846 | + .head_bucket(req) |
4847 | + .map_err(InitError::from) |
4848 | + .from_err() |
4849 | + .map(move |()| Backend { |
4850 | + client, |
4851 | + bucket, |
4852 | + prefix, |
4853 | + }) |
4854 | + } |
4855 | + |
4856 | + fn key_to_path(&self, oid: &Oid) -> String { |
4857 | + format!("{}/{}", self.prefix, oid.path()) |
4858 | + } |
4859 | + } |
4860 | + |
4861 | + impl<C> Storage for Backend<C> |
4862 | + where |
4863 | + C: S3, |
4864 | + { |
4865 | + type Error = Error; |
4866 | + |
4867 | + fn get(&self, key: &Oid) -> StorageFuture<Option<LFSObject>, Self::Error> { |
4868 | + let request = GetObjectRequest { |
4869 | + bucket: self.bucket.clone(), |
4870 | + key: self.key_to_path(key), |
4871 | + response_content_type: Some("application/octet-stream".into()), |
4872 | + ..Default::default() |
4873 | + }; |
4874 | + |
4875 | + Box::new( |
4876 | + self.client |
4877 | + .get_object(request) |
4878 | + .then(move |result| match result { |
4879 | + Ok(object) => Ok(Some(LFSObject::new( |
4880 | + object.content_length.unwrap() as u64, |
4881 | + Box::new(object.body.unwrap().map(Bytes::from)), |
4882 | + ))), |
4883 | + Err(err) => { |
4884 | + if let GetObjectError::NoSuchKey(_) = &err { |
4885 | + Ok(None) |
4886 | + } else { |
4887 | + Err(err) |
4888 | + } |
4889 | + } |
4890 | + }) |
4891 | + .from_err(), |
4892 | + ) |
4893 | + } |
4894 | + |
4895 | + fn put( |
4896 | + &self, |
4897 | + key: &Oid, |
4898 | + value: LFSObject, |
4899 | + ) -> StorageFuture<(), Self::Error> { |
4900 | + // TODO: |
4901 | + // * Put an upper limit on the amount of data that can be uploaded. |
4902 | + // * Encrypt the data with a private key(?) |
4903 | + let (len, stream) = value.into_parts(); |
4904 | + |
4905 | + let stream = stream.map(|chunk| { |
4906 | + // Since Rusoto doesn't use a reference counted chunk of bytes, |
4907 | + // we must copy to a new `Vec<u8>` here. See: |
4908 | + // https://github.com/rusoto/rusoto/issues/1028 |
4909 | + Vec::from(chunk.as_ref()) |
4910 | + }); |
4911 | + |
4912 | + let request = PutObjectRequest { |
4913 | + bucket: self.bucket.clone(), |
4914 | + key: self.key_to_path(key), |
4915 | + content_length: Some(len as i64), |
4916 | + body: Some(StreamingBody::new(stream)), |
4917 | + ..Default::default() |
4918 | + }; |
4919 | + |
4920 | + Box::new(self.client.put_object(request).map(|_| ()).from_err()) |
4921 | + } |
4922 | + |
4923 | + fn size(&self, key: &Oid) -> StorageFuture<Option<u64>, Self::Error> { |
4924 | + let request = HeadObjectRequest { |
4925 | + bucket: self.bucket.clone(), |
4926 | + key: self.key_to_path(key), |
4927 | + ..Default::default() |
4928 | + }; |
4929 | + |
4930 | + Box::new( |
4931 | + self.client |
4932 | + .head_object(request) |
4933 | + .then(|result| match result { |
4934 | + Ok(object) => { |
4935 | + Ok(Some(object.content_length.unwrap() as u64)) |
4936 | + } |
4937 | + Err(err) => { |
4938 | + match &err { |
4939 | + HeadObjectError::Unknown(e) => { |
4940 | + // There is a bug in Rusoto that causes it to |
4941 | + // always return an "unknown" error when the key |
4942 | + // does not exist. Thus we must check the error |
4943 | + // code manually. See: |
4944 | + // https://github.com/rusoto/rusoto/issues/716 |
4945 | + if e.status == 404 { |
4946 | + Ok(None) |
4947 | + } else { |
4948 | + Err(err) |
4949 | + } |
4950 | + } |
4951 | + HeadObjectError::NoSuchKey(_) => Ok(None), |
4952 | + _ => Err(err), |
4953 | + } |
4954 | + } |
4955 | + }) |
4956 | + .from_err(), |
4957 | + ) |
4958 | + } |
4959 | + |
4960 | + /// This never deletes objects from S3 and always returns success. |
4961 | + fn delete(&self, _key: &Oid) -> StorageFuture<(), Self::Error> { |
4962 | + Box::new(future::ok(())) |
4963 | + } |
4964 | + |
4965 | + /// Always returns an empty stream. This may be changed in the future. |
4966 | + fn list(&self) -> StorageStream<(Oid, u64), Self::Error> { |
4967 | + Box::new(stream::empty()) |
4968 | + } |
4969 | + } |
4970 | diff --git a/src/storage/verify.rs b/src/storage/verify.rs |
4971 | new file mode 100644 |
4972 | index 0000000..32ac44f |
4973 | --- /dev/null |
4974 | +++ b/src/storage/verify.rs |
4975 | @@ -0,0 +1,146 @@ |
4976 | + // Copyright (c) 2019 Jason White |
4977 | + // |
4978 | + // Permission is hereby granted, free of charge, to any person obtaining a copy |
4979 | + // of this software and associated documentation files (the "Software"), to deal |
4980 | + // in the Software without restriction, including without limitation the rights |
4981 | + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
4982 | + // copies of the Software, and to permit persons to whom the Software is |
4983 | + // furnished to do so, subject to the following conditions: |
4984 | + // |
4985 | + // The above copyright notice and this permission notice shall be included in |
4986 | + // all copies or substantial portions of the Software. |
4987 | + // |
4988 | + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
4989 | + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
4990 | + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
4991 | + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
4992 | + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
4993 | + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
4994 | + // SOFTWARE. |
4995 | + use std::io; |
4996 | + use std::sync::Arc; |
4997 | + |
4998 | + use derive_more::{Display, From}; |
4999 | + use futures::{ |
5000 | + future::{self, Either}, |
5001 | + Future, Stream, |
5002 | + }; |
5003 | + |
5004 | + use crate::lfs::Oid; |
5005 | + use crate::sha256::{Sha256VerifyError, VerifyStream}; |
5006 | + |
5007 | + use super::{LFSObject, Storage, StorageFuture, StorageStream}; |
5008 | + |
5009 | + #[derive(Debug, Display, From)] |
5010 | + enum Error { |
5011 | + Verify(Sha256VerifyError), |
5012 | + Io(io::Error), |
5013 | + } |
5014 | + |
5015 | + impl std::error::Error for Error {} |
5016 | + |
5017 | + /// Verifies LFS objects as they are uploaded or downloaded. |
5018 | + /// |
5019 | + /// If corruption is detected upon upload, the object is rejected. |
5020 | + /// |
5021 | + /// If corruption is detected upon download, the object is deleted from the |
5022 | + /// underlying storage backend. |
5023 | + /// |
5024 | + /// Note that this must be composed with decrypted data. Otherwise, the |
5025 | + /// verification will always fail. Thus, this should be the last thing between |
5026 | + /// the client and the other storage backends. |
5027 | + pub struct Backend<S> { |
5028 | + storage: Arc<S>, |
5029 | + } |
5030 | + |
5031 | + impl<S> Backend<S> { |
5032 | + pub fn new(storage: S) -> Self { |
5033 | + Backend { |
5034 | + storage: Arc::new(storage), |
5035 | + } |
5036 | + } |
5037 | + } |
5038 | + |
5039 | + impl<S> Storage for Backend<S> |
5040 | + where |
5041 | + S: Storage + Send + Sync + 'static, |
5042 | + S::Error: 'static, |
5043 | + { |
5044 | + type Error = S::Error; |
5045 | + |
5046 | + /// Verifies that the downloaded object has a valid SHA256. If it doesn't, |
5047 | + /// then it means our storage is corrupted. Thus, we automatically delete it |
5048 | + /// from storage if it is corrupted. If storage backends are composed |
5049 | + /// correctly, then it should only delete cache storage (not permanent |
5050 | + /// storage). |
5051 | + fn get(&self, key: &Oid) -> StorageFuture<Option<LFSObject>, Self::Error> { |
5052 | + let key = *key; |
5053 | + |
5054 | + let storage = self.storage.clone(); |
5055 | + |
5056 | + Box::new(self.storage.get(&key).map(move |obj| match obj { |
5057 | + Some(obj) => { |
5058 | + let (len, stream) = obj.into_parts(); |
5059 | + |
5060 | + let stream = |
5061 | + VerifyStream::new(stream.map_err(Error::from), len, key) |
5062 | + .or_else(move |err| match err { |
5063 | + Error::Verify(err) => { |
5064 | + log::error!( |
5065 | + "Deleting corrupted object {} ({})", |
5066 | + key, |
5067 | + err |
5068 | + ); |
5069 | + |
5070 | + Either::A(storage.delete(&key).then(|_| { |
5071 | + Err(io::Error::new( |
5072 | + io::ErrorKind::Other, |
5073 | + "found corrupted object", |
5074 | + )) |
5075 | + })) |
5076 | + } |
5077 | + Error::Io(err) => Either::B(future::err(err)), |
5078 | + }); |
5079 | + |
5080 | + Some(LFSObject::new(len, Box::new(stream))) |
5081 | + } |
5082 | + None => None, |
5083 | + })) |
5084 | + } |
5085 | + |
5086 | + fn put( |
5087 | + &self, |
5088 | + key: &Oid, |
5089 | + value: LFSObject, |
5090 | + ) -> StorageFuture<(), Self::Error> { |
5091 | + let (len, stream) = value.into_parts(); |
5092 | + |
5093 | + let stream = VerifyStream::new(stream.map_err(Error::from), len, *key) |
5094 | + .map_err(move |err| match err { |
5095 | + Error::Verify(err) => io::Error::new(io::ErrorKind::Other, err), |
5096 | + Error::Io(err) => io::Error::new(io::ErrorKind::Other, err), |
5097 | + }); |
5098 | + |
5099 | + self.storage.put(key, LFSObject::new(len, Box::new(stream))) |
5100 | + } |
5101 | + |
5102 | + fn size(&self, key: &Oid) -> StorageFuture<Option<u64>, Self::Error> { |
5103 | + self.storage.size(key) |
5104 | + } |
5105 | + |
5106 | + fn delete(&self, key: &Oid) -> StorageFuture<(), Self::Error> { |
5107 | + self.storage.delete(key) |
5108 | + } |
5109 | + |
5110 | + fn list(&self) -> StorageStream<(Oid, u64), Self::Error> { |
5111 | + self.storage.list() |
5112 | + } |
5113 | + |
5114 | + fn total_size(&self) -> Option<u64> { |
5115 | + self.storage.total_size() |
5116 | + } |
5117 | + |
5118 | + fn max_size(&self) -> Option<u64> { |
5119 | + self.storage.max_size() |
5120 | + } |
5121 | + } |