@@ -9,6 +9,7 @@ use hex::ToHex;
99use hyper:: body:: Buf ;
1010use sha2:: { Digest , Sha256 } ;
1111use tokio:: runtime:: Handle ;
12+ use url:: Url ;
1213
1314use crate :: controllers:: cargo_prelude:: * ;
1415use crate :: models:: {
@@ -145,7 +146,10 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
145146
146147 let license_file = metadata. license_file . as_deref ( ) ;
147148
148- persist. validate ( ) ?;
149+ validate_url ( persist. homepage , "homepage" ) ?;
150+ validate_url ( persist. documentation , "documentation" ) ?;
151+ validate_url ( persist. repository , "repository" ) ?;
152+
149153 if is_reserved_name ( persist. name , conn) ? {
150154 return Err ( cargo_err ( "cannot upload a crate with a reserved name" ) ) ;
151155 }
@@ -347,6 +351,26 @@ fn is_reserved_name(name: &str, conn: &mut PgConnection) -> QueryResult<bool> {
347351 . get_result ( conn)
348352}
349353
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+
350374fn missing_metadata_error_message ( missing : & [ & str ] ) -> String {
351375 format ! (
352376 "missing or empty metadata fields: {}. Please \
@@ -453,7 +477,12 @@ impl From<TarballError> for BoxedAppError {
453477
454478#[ cfg( test) ]
455479mod 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+ }
457486
458487 #[ test]
459488 fn missing_metadata_error_message_test ( ) {
0 commit comments