forked from EmbarkStudios/gsutil
-
Notifications
You must be signed in to change notification settings - Fork 0
/
signurl.rs
119 lines (102 loc) · 3.2 KB
/
signurl.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use crate::util;
use anyhow::{anyhow, Error};
use tame_gcs::{http, signed_url, signing};
#[derive(clap::ArgEnum, Copy, Clone, Debug)]
pub enum Method {
Get,
Post,
Put,
Delete,
Head,
Options,
Connect,
Patch,
Trace,
Resumable,
}
fn parse_duration(src: &str) -> Result<std::time::Duration, Error> {
use std::time::Duration;
let suffix_pos = src.find(char::is_alphabetic).unwrap_or(src.len());
let num: u64 = src[..suffix_pos].parse()?;
let suffix = if suffix_pos == src.len() {
"h"
} else {
&src[suffix_pos..]
};
let duration = match suffix {
"s" | "S" => Duration::from_secs(num),
"m" | "M" => Duration::from_secs(num * 60),
"h" | "H" => Duration::from_secs(num * 60 * 60),
"d" | "D" => Duration::from_secs(num * 60 * 60 * 24),
s => return Err(anyhow!("unknown duration suffix '{}'", s)),
};
Ok(duration)
}
#[derive(clap::Parser, Debug)]
pub struct Args {
/// The HTTP method to be used with the signed url.
#[clap(arg_enum, short, default_value = "GET", ignore_case = true)]
method: Method,
#[clap(
short,
default_value = "1h",
parse(try_from_str = parse_duration),
long_help = "The duration that ths signed url will be valid for.
Times may be specified with no suffix (default hours), or one of:
* (s)econds
* (m)inutes
* (h)ours
* (d)ays
"
)]
duration: std::time::Duration,
/// The content-type for which the url is valid for, eg. "application/json"
#[structopt(short)]
content_type: Option<String>,
/// The gs:// url
url: url::Url,
}
pub async fn cmd(cred_path: std::path::PathBuf, args: Args) -> Result<(), Error> {
let oid = util::gs_url_to_object_id(&args.url)?;
let url_signer = signed_url::UrlSigner::with_ring();
let service_account = signing::ServiceAccount::load_json_file(&cred_path)?;
let mut options = signed_url::SignedUrlOptional {
duration: args.duration,
..Default::default()
};
if let Some(content_type) = args.content_type {
options.headers.insert(
http::header::CONTENT_TYPE,
http::header::HeaderValue::from_str(&content_type)?,
);
}
options.method = match args.method {
Method::Get => http::Method::GET,
Method::Post => http::Method::POST,
Method::Put => http::Method::PUT,
Method::Delete => http::Method::DELETE,
Method::Head => http::Method::HEAD,
Method::Options => http::Method::OPTIONS,
Method::Connect => http::Method::CONNECT,
Method::Patch => http::Method::PATCH,
Method::Trace => http::Method::TRACE,
Method::Resumable => {
options.headers.insert(
http::header::HeaderName::from_static("x-goog-resumable"),
http::header::HeaderValue::from_static("start"),
);
http::Method::POST
}
};
let signed_url = url_signer.generate(
&service_account,
&(
oid.bucket(),
oid.object()
.ok_or_else(|| anyhow!("must have a valid object name"))?,
),
options,
)?;
println!("{}", signed_url);
Ok(())
}