@@ -709,6 +709,27 @@ mod tests {
709709 ) ;
710710 }
711711
712+ /// Covers supported RFC 2047 Q words and unsupported metadata encodings.
713+ #[ test]
714+ fn decodes_rfc2047_q_metadata_and_rejects_unsupported_words ( ) {
715+ assert_eq ! (
716+ decode_rfc2047_header_value( "=?UTF-8?Q?Hello_World=C3=A9?=" ) . as_deref( ) ,
717+ Some ( "Hello World\u{e9} " )
718+ ) ;
719+ assert_eq ! (
720+ decode_rfc2047_header_value( "=?utf-8?q?=c3=a9?=" ) . as_deref( ) ,
721+ Some ( "\u{e9} " )
722+ ) ;
723+ assert_eq ! (
724+ decode_rfc2047_header_value( "=?us-ascii?Q?AZaz09=21?=" ) . as_deref( ) ,
725+ Some ( "AZaz09!" )
726+ ) ;
727+ assert_eq ! ( decode_rfc2047_header_value( "=?iso-8859-1?Q?caf=E9?=" ) , None ) ;
728+ assert_eq ! ( decode_rfc2047_header_value( "=?UTF-8?X?abc?=" ) , None ) ;
729+ assert_eq ! ( decode_rfc2047_header_value( "=?UTF-8?Q?bad=GG?=" ) , None ) ;
730+ assert_eq ! ( decode_rfc2047_header_value( "plain" ) , None ) ;
731+ }
732+
712733 #[ tokio:: test]
713734 async fn rewrites_object_lock_invalid_retention_error_code ( ) -> Result < ( ) , HttpError > {
714735 let mut response = HttpResponse :: new ( Body :: from (
@@ -725,6 +746,97 @@ mod tests {
725746 Ok ( ( ) )
726747 }
727748
749+ /// Keeps XML error rewrites scoped to matching status, marker, and UTF-8 bodies.
750+ #[ tokio:: test]
751+ async fn leaves_non_target_xml_error_rewrites_alone ( ) -> Result < ( ) , HttpError > {
752+ let mut wrong_status = HttpResponse :: new ( Body :: from (
753+ "<Error><Code>InvalidRequest</Code><Message>InvalidRetentionPeriod</Message></Error>"
754+ . to_string ( ) ,
755+ ) ) ;
756+ * wrong_status. status_mut ( ) = StatusCode :: NOT_FOUND ;
757+ let wrong_status = rewrite_invalid_retention_period_code ( wrong_status) . await ?;
758+ assert_eq ! ( wrong_status. status( ) , StatusCode :: NOT_FOUND ) ;
759+ assert ! (
760+ response_body_text( wrong_status)
761+ . await ?
762+ . contains( "InvalidRequest" )
763+ ) ;
764+
765+ let mut missing_marker = HttpResponse :: new ( Body :: from (
766+ "<Error><Code>InvalidRequest</Code><Message>other</Message></Error>" . to_string ( ) ,
767+ ) ) ;
768+ * missing_marker. status_mut ( ) = StatusCode :: BAD_REQUEST ;
769+ let missing_marker = rewrite_invalid_retention_period_code ( missing_marker) . await ?;
770+ assert ! (
771+ response_body_text( missing_marker)
772+ . await ?
773+ . contains( "InvalidRequest" )
774+ ) ;
775+
776+ let mut non_utf8 = HttpResponse :: new ( Body :: from ( Bytes :: from_static ( & [ 0xff , b'<' ] ) ) ) ;
777+ * non_utf8. status_mut ( ) = StatusCode :: BAD_REQUEST ;
778+ let non_utf8 = rewrite_invalid_retention_period_code ( non_utf8) . await ?;
779+ let bytes = non_utf8
780+ . into_body ( )
781+ . collect ( )
782+ . await
783+ . map_err ( HttpError :: new) ?
784+ . to_bytes ( ) ;
785+ assert_eq ! ( bytes. as_ref( ) , & [ 0xff , b'<' ] ) ;
786+
787+ Ok ( ( ) )
788+ }
789+
790+ /// Covers POST Object status compatibility rewrites for parsed policy errors.
791+ #[ tokio:: test]
792+ async fn rewrites_post_policy_condition_statuses ( ) -> Result < ( ) , HttpError > {
793+ let mut invalid_policy = HttpResponse :: new ( Body :: from (
794+ "<Error><Code>InvalidPolicyDocument</Code><Message>Policy condition failed</Message></Error>"
795+ . to_string ( ) ,
796+ ) ) ;
797+ * invalid_policy. status_mut ( ) = StatusCode :: BAD_REQUEST ;
798+ let invalid_policy = rewrite_post_policy_condition_status ( invalid_policy) . await ?;
799+ assert_eq ! ( invalid_policy. status( ) , StatusCode :: FORBIDDEN ) ;
800+
801+ let mut access_denied = HttpResponse :: new ( Body :: from (
802+ "<Error><Code>AccessDenied</Code><Message>condition mismatch</Message></Error>"
803+ . to_string ( ) ,
804+ ) ) ;
805+ * access_denied. status_mut ( ) = StatusCode :: FORBIDDEN ;
806+ let access_denied = rewrite_post_policy_condition_status ( access_denied) . await ?;
807+ assert_eq ! ( access_denied. status( ) , StatusCode :: BAD_REQUEST ) ;
808+
809+ let mut expired = HttpResponse :: new ( Body :: from (
810+ "<Error><Code>AccessDenied</Code><Message>Request has expired</Message></Error>"
811+ . to_string ( ) ,
812+ ) ) ;
813+ * expired. status_mut ( ) = StatusCode :: FORBIDDEN ;
814+ let expired = rewrite_post_policy_condition_status ( expired) . await ?;
815+ assert_eq ! ( expired. status( ) , StatusCode :: FORBIDDEN ) ;
816+
817+ let mut field_missing = HttpResponse :: new ( Body :: from (
818+ "<Error><Code>AccessDenied</Code><Message>Each form field must appear</Message></Error>"
819+ . to_string ( ) ,
820+ ) ) ;
821+ * field_missing. status_mut ( ) = StatusCode :: FORBIDDEN ;
822+ let field_missing = rewrite_post_policy_condition_status ( field_missing) . await ?;
823+ assert_eq ! ( field_missing. status( ) , StatusCode :: FORBIDDEN ) ;
824+
825+ let mut non_utf8 = HttpResponse :: new ( Body :: from ( Bytes :: from_static ( & [ 0xff , b'a' ] ) ) ) ;
826+ * non_utf8. status_mut ( ) = StatusCode :: FORBIDDEN ;
827+ let non_utf8 = rewrite_post_policy_condition_status ( non_utf8) . await ?;
828+ assert_eq ! ( non_utf8. status( ) , StatusCode :: FORBIDDEN ) ;
829+ let bytes = non_utf8
830+ . into_body ( )
831+ . collect ( )
832+ . await
833+ . map_err ( HttpError :: new) ?
834+ . to_bytes ( ) ;
835+ assert_eq ! ( bytes. as_ref( ) , & [ 0xff , b'a' ] ) ;
836+
837+ Ok ( ( ) )
838+ }
839+
728840 #[ test]
729841 fn presigned_expiry_validation_matches_compat_contract ( )
730842 -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
@@ -868,6 +980,10 @@ mod tests {
868980 ) ,
869981 "EpELyoQzABuh9lHQ+KwND1xJ+Uc="
870982 ) ;
983+ assert_eq ! (
984+ sig_v2_signature( & "k" . repeat( 65 ) , "data" ) ,
985+ "zmngYEpYGiC5dY+v6heIjy+/Few="
986+ ) ;
871987 }
872988
873989 #[ test]
@@ -924,6 +1040,22 @@ mod tests {
9241040 Ok ( ( ) )
9251041 }
9261042
1043+ /// Leaves create-bucket `SigV2` authorization alone when the slash signature is invalid.
1044+ #[ test]
1045+ fn skips_create_bucket_sig_v2_rewrite_for_signature_mismatch ( ) -> Result < ( ) , hyper:: http:: Error >
1046+ {
1047+ let auth = AuthorizationV2 {
1048+ access_key : "access" . to_string ( ) ,
1049+ signature : "signature" . to_string ( ) ,
1050+ } ;
1051+ let mut req = request ( Method :: PUT , "/bucket" ) ?;
1052+
1053+ rewrite_create_bucket_sig_v2 ( & mut req, "secret" , & auth) ;
1054+
1055+ assert ! ( req. headers( ) . get( header:: AUTHORIZATION ) . is_none( ) ) ;
1056+ Ok ( ( ) )
1057+ }
1058+
9271059 #[ test]
9281060 fn sig_v2_header_compat_rejects_malformed_auth_and_rewrites_valid_bucket_request ( )
9291061 -> Result < ( ) , hyper:: http:: Error > {
0 commit comments