Skip to content

Commit 44aeb47

Browse files
committed
Require a User-Agent header, take 2
We deployed this back in October, and quickly realized that old versions of Cargo (Rust 1.15 and earlier) didn't set a User-Agent header. Notably, the version of Cargo used for bootstrapping the compiler back then was this old, so we accidentally broke the build for rustc. We still see build traffic for these old versions, and we likely will never be willing to break `cargo build` for old versions of Cargo. However, as we discussed back in early November, we're fine with doing this for all other endpoints. This change will break versions of Cargo prior to Rust 1.16 for all operations that talk to crates.io, except for `cargo build`. We no longer receive any traffic for publish, yank, unyank, or owners from these old versions. (`cargo search` hits an endpoint that is also hit by bots, so I can't say for sure if it's coming from cargo or random crawlers, which is kinda the point of this requirement in the first place)
1 parent 59a4ca9 commit 44aeb47

File tree

4 files changed

+50
-33
lines changed

4 files changed

+50
-33
lines changed

src/middleware.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,8 @@ pub fn build_middleware(app: Arc<App>, endpoints: R404) -> MiddlewareBuilder {
9393
let ips = ip_list.split(',').map(String::from).collect();
9494
m.around(block_ips::BlockIps::new(ips));
9595
}
96-
// Note: Temporarily disabled because cargo-vendor doesn't include a
97-
// User-Agent header and Rust's CI broke. If this is still commented out
98-
// by Nov 7, 2018 ping the crates.io team.
99-
// m.around(require_user_agent::RequireUserAgent::default());
96+
97+
m.around(require_user_agent::RequireUserAgent::default());
10098

10199
if env != Env::Test {
102100
m.around(log_request::LogRequests::default());

src/middleware/require_user_agent.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ impl AroundMiddleware for RequireUserAgent {
2222
impl Handler for RequireUserAgent {
2323
fn call(&self, req: &mut dyn Request) -> Result<Response, Box<dyn Error + Send>> {
2424
let has_user_agent = request_header(req, "User-Agent") != "";
25-
if !has_user_agent {
25+
let is_download = req.path().ends_with("download");
26+
if !has_user_agent && !is_download {
2627
let body = format!(
2728
include_str!("no_user_agent_message.txt"),
2829
request_header(req, "X-Request-Id"),

src/tests/server.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1-
// Note: Temporarily disabled because cargo-vendor doesn't include a
2-
// User-Agent header and Rust's CI broke. If this is still commented out
3-
// by Nov 7, 2018 ping the crates.io team.
1+
use conduit::Method;
42

5-
// use conduit::{Handler, Method};
6-
//
7-
// use {app, req};
8-
//
9-
//
10-
// #[test]
11-
// fn user_agent_is_required() {
12-
// let (_b, _app, middle) = app();
13-
//
14-
// let mut req = req(Method::Get, "/api/v1/crates");
15-
// req.header("User-Agent", "");
16-
// let resp = t!(middle.call(&mut req));
17-
// assert_eq!(resp.status.0, 403);
18-
// }
3+
use crate::builders::*;
4+
use crate::util::*;
5+
6+
#[test]
7+
fn user_agent_is_required() {
8+
let (_app, anon) = TestApp::init().empty();
9+
10+
let mut req = anon.request_builder(Method::Get, "/api/v1/crates");
11+
req.header("User-Agent", "");
12+
let resp = anon.run::<()>(req);
13+
resp.assert_status(403);
14+
}
15+
16+
#[test]
17+
fn user_agent_is_not_required_for_download() {
18+
let (app, anon, user) = TestApp::init().with_user();
19+
20+
app.db(|conn| {
21+
CrateBuilder::new("dl_no_ua", user.as_model().id).expect_build(conn);
22+
});
23+
24+
let mut req = anon.request_builder(Method::Get, "/api/v1/crates/dl_no_ua/0.99.0/download");
25+
req.header("User-Agent", "");
26+
let resp = anon.run::<()>(req);
27+
resp.assert_status(302);
28+
}

src/tests/util.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,21 @@ pub trait RequestHelper {
145145
fn request_builder(&self, method: Method, path: &str) -> MockRequest;
146146
fn app(&self) -> &TestApp;
147147

148+
/// Run a request
149+
fn run<T>(&self, mut request: MockRequest) -> Response<T>
150+
where
151+
T: serde::de::DeserializeOwned,
152+
{
153+
Response::new(self.app().0.middle.call(&mut request))
154+
}
155+
148156
/// Issue a GET request
149157
fn get<T>(&self, path: &str) -> Response<T>
150158
where
151159
for<'de> T: serde::Deserialize<'de>,
152160
{
153-
let mut request = self.request_builder(Method::Get, path);
154-
Response::new(self.app().0.middle.call(&mut request))
161+
let request = self.request_builder(Method::Get, path);
162+
self.run(request)
155163
}
156164

157165
/// Issue a GET request that includes query parameters
@@ -161,36 +169,36 @@ pub trait RequestHelper {
161169
{
162170
let mut request = self.request_builder(Method::Get, path);
163171
request.with_query(query);
164-
Response::new(self.app().0.middle.call(&mut request))
172+
self.run(request)
165173
}
166174

167175
/// Issue a PUT request
168176
fn put<T>(&self, path: &str, body: &[u8]) -> Response<T>
169177
where
170178
for<'de> T: serde::Deserialize<'de>,
171179
{
172-
let mut builder = self.request_builder(Method::Put, path);
173-
let request = builder.with_body(body);
174-
Response::new(self.app().0.middle.call(request))
180+
let mut request = self.request_builder(Method::Put, path);
181+
request.with_body(body);
182+
self.run(request)
175183
}
176184

177185
/// Issue a DELETE request
178186
fn delete<T>(&self, path: &str) -> Response<T>
179187
where
180188
for<'de> T: serde::Deserialize<'de>,
181189
{
182-
let mut request = self.request_builder(Method::Delete, path);
183-
Response::new(self.app().0.middle.call(&mut request))
190+
let request = self.request_builder(Method::Delete, path);
191+
self.run(request)
184192
}
185193

186194
/// Issue a DELETE request with a body... yes we do it, for crate owner removal
187195
fn delete_with_body<T>(&self, path: &str, body: &[u8]) -> Response<T>
188196
where
189197
for<'de> T: serde::Deserialize<'de>,
190198
{
191-
let mut builder = self.request_builder(Method::Delete, path);
192-
let request = builder.with_body(body);
193-
Response::new(self.app().0.middle.call(request))
199+
let mut request = self.request_builder(Method::Delete, path);
200+
request.with_body(body);
201+
self.run(request)
194202
}
195203

196204
/// Search for crates matching a query string

0 commit comments

Comments
 (0)