@@ -9,6 +9,7 @@ use hex::ToHex;
9
9
use hyper:: body:: Buf ;
10
10
use sha2:: { Digest , Sha256 } ;
11
11
use tokio:: runtime:: Handle ;
12
+ use url:: Url ;
12
13
13
14
use crate :: controllers:: cargo_prelude:: * ;
14
15
use crate :: models:: {
@@ -145,7 +146,10 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
145
146
146
147
let license_file = metadata. license_file . as_deref ( ) ;
147
148
148
- persist. validate ( ) ?;
149
+ validate_url ( persist. homepage , "homepage" ) ?;
150
+ validate_url ( persist. documentation , "documentation" ) ?;
151
+ validate_url ( persist. repository , "repository" ) ?;
152
+
149
153
if is_reserved_name ( persist. name , conn) ? {
150
154
return Err ( cargo_err ( "cannot upload a crate with a reserved name" ) ) ;
151
155
}
@@ -347,6 +351,26 @@ fn is_reserved_name(name: &str, conn: &mut PgConnection) -> QueryResult<bool> {
347
351
. get_result ( conn)
348
352
}
349
353
354
+ fn validate_url ( url : Option < & str > , field : & str ) -> AppResult < ( ) > {
355
+ let url = match url {
356
+ Some ( s) => s,
357
+ None => return Ok ( ( ) ) ,
358
+ } ;
359
+
360
+ // Manually check the string, as `Url::parse` may normalize relative URLs
361
+ // making it difficult to ensure that both slashes are present.
362
+ if !url. starts_with ( "http://" ) && !url. starts_with ( "https://" ) {
363
+ return Err ( cargo_err ( & format_args ! (
364
+ "URL for field `{field}` must begin with http:// or https:// (url: {url})"
365
+ ) ) ) ;
366
+ }
367
+
368
+ // Ensure the entire URL parses as well
369
+ Url :: parse ( url)
370
+ . map_err ( |_| cargo_err ( & format_args ! ( "`{field}` is not a valid url: `{url}`" ) ) ) ?;
371
+ Ok ( ( ) )
372
+ }
373
+
350
374
fn missing_metadata_error_message ( missing : & [ & str ] ) -> String {
351
375
format ! (
352
376
"missing or empty metadata fields: {}. Please \
@@ -453,7 +477,12 @@ impl From<TarballError> for BoxedAppError {
453
477
454
478
#[ cfg( test) ]
455
479
mod tests {
456
- use super :: missing_metadata_error_message;
480
+ use super :: { missing_metadata_error_message, validate_url} ;
481
+
482
+ #[ test]
483
+ fn deny_relative_urls ( ) {
484
+ assert_err ! ( validate_url( Some ( "https:/example.com/home" ) , "homepage" ) ) ;
485
+ }
457
486
458
487
#[ test]
459
488
fn missing_metadata_error_message_test ( ) {
0 commit comments