@@ -716,6 +716,62 @@ async fn test_invitation_crate_id_mismatch() {
716716 assert_snapshot ! ( response. text( ) , @r#"{"errors":[{"detail":"`crate_id` mismatch"}]}"# ) ;
717717}
718718
719+ #[ tokio:: test( flavor = "multi_thread" ) ]
720+ async fn test_invitation_with_null_byte ( ) {
721+ let ( app, _anon, _owner) = TestApp :: full ( ) . with_user ( ) . await ;
722+ let invited_user = app. db_new_user ( "user_bar" ) . await ;
723+
724+ fn make_body < T : serde:: Serialize > (
725+ krate_name : & str ,
726+ krate_id : T ,
727+ accepted : bool ,
728+ ) -> serde_json:: Value {
729+ json ! ( {
730+ "crate_owner_invite" : {
731+ "invited_by_username" : "" ,
732+ "crate_name" : krate_name,
733+ "crate_id" : krate_id,
734+ "created_at" : "" ,
735+ "accepted" : accepted
736+ }
737+ } )
738+ }
739+
740+ // "foo%00bar" in the path, "foobar" in the body
741+
742+ let accepted = true ;
743+ let url = "/api/v1/me/crate_owner_invitations/foo%00bar" ;
744+ let body = make_body ( "foobar" , "foobar" , accepted) ;
745+ let response = invited_user. put :: < ( ) > ( url, body. to_string ( ) ) . await ;
746+ assert_snapshot ! ( response. status( ) , @"404 Not Found" ) ;
747+ assert_snapshot ! ( response. text( ) , @r#"{"errors":[{"detail":"crate `foo\u0000bar` does not exist"}]}"# ) ;
748+
749+ let accepted = false ;
750+ let url = "/api/v1/me/crate_owner_invitations/foo%00bar" ;
751+ let body = make_body ( "foobar" , "foobar" , accepted) ;
752+ let response = invited_user. put :: < ( ) > ( url, body. to_string ( ) ) . await ;
753+ assert_snapshot ! ( response. status( ) , @"404 Not Found" ) ;
754+ assert_snapshot ! ( response. text( ) , @r#"{"errors":[{"detail":"crate `foo\u0000bar` does not exist"}]}"# ) ;
755+
756+ // "foobar" in the path, "foo%00bar" in the body
757+ // this will be treated as a mismatch and return 400 instead of 404 because we only validate the
758+ // name in the path.
759+
760+ let accepted = true ;
761+ let url = "/api/v1/me/crate_owner_invitations/foobar" ;
762+ let body = make_body ( "foobar" , "foo%00bar" , accepted) ;
763+ let response = invited_user. put :: < ( ) > ( url, body. to_string ( ) ) . await ;
764+ assert_snapshot ! ( response. status( ) , @"400 Bad Request" ) ;
765+ assert_snapshot ! ( response. text( ) , @r#"{"errors":[{"detail":"`crate_id` mismatch"}]}"# ) ;
766+
767+ let accepted = false ;
768+ let url = "/api/v1/me/crate_owner_invitations/foobar" ;
769+ let body = make_body ( "foobar" , "foo%00bar" , accepted) ;
770+ let response = invited_user. put :: < ( ) > ( url, body. to_string ( ) ) . await ;
771+ assert_snapshot ! ( response. status( ) , @"400 Bad Request" ) ;
772+ assert_snapshot ! ( response. text( ) , @r#"{"errors":[{"detail":"`crate_id` mismatch"}]}"# ) ;
773+ }
774+
719775/// Hacky way to simulate the expiration of an ownership invitation. Instead of letting a month
720776/// pass, the creation date of the invite is moved back a month.
721777pub async fn expire_invitation ( app : & TestApp , crate_id : i32 ) {
0 commit comments