@@ -21,6 +21,28 @@ use self::config::{extract_brs_config, Config};
2121
2222use super :: constants:: BASE_URL ;
2323
24+ lazy_static:: lazy_static! {
25+ /// Regex to extract OTP parameters from the authentication payload.
26+ /// Matches: data-strong-authentication-payload="{...}">
27+ static ref OTP_PARAMS_REGEX : Regex = Regex :: new( r#"data-strong-authentication-payload="(\{.*?\})">"# )
28+ . expect( "Failed to compile OTP parameters regex" ) ;
29+
30+ /// Regex to extract the __brs_mit cookie value from the response.
31+ /// Matches: __brs_mit=<value>;
32+ static ref BRS_MIT_COOKIE_REGEX : Regex = Regex :: new( r"(?m)__brs_mit=(?P<brs_mit_cookie>.*?);" )
33+ . expect( "Failed to compile __brs_mit cookie regex" ) ;
34+
35+ /// Regex to extract the form token from the login page.
36+ /// Matches: form[_token]" ... value="<token>" >
37+ static ref TOKEN_REGEX : Regex = Regex :: new( r#"(?ms)form\[_token\]"(.*?)value="(?P<token>.*?)"\s*>"# )
38+ . expect( "Failed to compile form token regex" ) ;
39+
40+ /// Regex to extract the user contact information from the response.
41+ /// Matches: userContact":"<contact>"
42+ static ref USER_CONTACT_REGEX : Regex = Regex :: new( r"(?m)userContact":"(?P<contact_user>.*?)"" )
43+ . expect( "Failed to compile user contact regex" ) ;
44+ }
45+
2446pub struct BoursoWebClient {
2547 /// The client used to make requests to the Bourso website.
2648 client : reqwest:: Client ,
@@ -513,24 +535,29 @@ impl BoursoWebClient {
513535///
514536/// The __brs_mit cookie as a string.
515537fn extract_brs_mit_cookie ( res : & str ) -> Result < String > {
516- let regex = Regex :: new ( r"(?m)__brs_mit=(?P<brs_mit_cookie>.*?);" ) . unwrap ( ) ;
517- let captures = regex. captures ( & res) ;
518-
519- if captures. is_none ( ) {
520- error ! ( "{}" , res) ;
521- bail ! ( "Could not extract brs mit cookie" ) ;
522- }
523-
524- let brs_mit_cookie = captures. unwrap ( ) . name ( "brs_mit_cookie" ) . unwrap ( ) ;
525-
526- Ok ( brs_mit_cookie. as_str ( ) . to_string ( ) )
538+ let brs_mit_cookie = BRS_MIT_COOKIE_REGEX
539+ . captures ( & res)
540+ . and_then ( |c| c. name ( "brs_mit_cookie" ) )
541+ . map ( |m| m. as_str ( ) . to_string ( ) )
542+ . ok_or_else ( || {
543+ error ! ( "{}" , res) ;
544+ anyhow:: anyhow!( "Could not extract brs mit cookie" )
545+ } ) ?;
546+
547+ Ok ( brs_mit_cookie)
527548}
528549
529550fn extract_token ( res : & str ) -> Result < String > {
530- let regex = Regex :: new ( r#"(?ms)form\[_token\]"(.*?)value="(?P<token>.*?)"\s*>"# ) . unwrap ( ) ;
531- let token = regex. captures ( & res) . unwrap ( ) . name ( "token" ) . unwrap ( ) ;
532-
533- Ok ( token. as_str ( ) . trim ( ) . to_string ( ) )
551+ let token = TOKEN_REGEX
552+ . captures ( & res)
553+ . and_then ( |c| c. name ( "token" ) )
554+ . map ( |m| m. as_str ( ) . trim ( ) . to_string ( ) )
555+ . ok_or_else ( || {
556+ error ! ( "{}" , res) ;
557+ anyhow:: anyhow!( "Could not extract form token" )
558+ } ) ?;
559+
560+ Ok ( token)
534561}
535562
536563/// Extract OTP parameters from the response string.
@@ -540,19 +567,20 @@ fn extract_token(res: &str) -> Result<String> {
540567/// # Returns
541568/// A tuple containing the resource ID and form state as strings.
542569fn extract_otp_params ( res : & str ) -> Result < ( String , String ) > {
543- let regex = Regex :: new ( r#"data-strong-authentication-payload="(\{.*?\})">"# ) ;
544-
545- let captures = regex. unwrap ( ) . captures ( & res) ;
546-
547- let challenge_json = if let Some ( captures) = captures {
548- let challenge_str = captures. get ( 1 ) . unwrap ( ) . as_str ( ) ;
549- // HTML decode the JSON string (replace " with ")
550- let decoded = challenge_str. replace ( """ , "\" " ) ;
551- serde_json:: from_str :: < serde_json:: Value > ( & decoded) ?
552- } else {
553- error ! ( "{}" , res) ;
554- bail ! ( "Could not extract authentication challenge parameters" ) ;
555- } ;
570+ let challenge_json = OTP_PARAMS_REGEX
571+ . captures ( & res)
572+ . and_then ( |c| c. get ( 1 ) )
573+ . map ( |m| m. as_str ( ) )
574+ . ok_or_else ( || {
575+ error ! ( "{}" , res) ;
576+ anyhow:: anyhow!( "Could not extract authentication challenge parameters" )
577+ } )
578+ . and_then ( |challenge_str| {
579+ // HTML decode the JSON string (replace " with ")
580+ let decoded = challenge_str. replace ( """ , "\" " ) ;
581+ serde_json:: from_str :: < serde_json:: Value > ( & decoded)
582+ . map_err ( |e| anyhow:: anyhow!( "Could not parse authentication challenge JSON: {}" , e) )
583+ } ) ?;
556584
557585 Ok ( (
558586 challenge_json[ "challenges" ] [ 0 ] [ "parameters" ] [ "formScreen" ] [ "actions" ] [ "check" ] [ "api" ]
@@ -575,10 +603,16 @@ fn extract_otp_params(res: &str) -> Result<(String, String)> {
575603}
576604
577605fn extract_user_contact ( res : & str ) -> Result < String > {
578- let regex = Regex :: new ( r"(?m)userContact":"(?P<contact_user>.*?)"" ) . unwrap ( ) ;
579- let contact_user = regex. captures ( & res) . unwrap ( ) . name ( "contact_user" ) . unwrap ( ) ;
580-
581- Ok ( contact_user. as_str ( ) . trim ( ) . to_string ( ) )
606+ let contact_user = USER_CONTACT_REGEX
607+ . captures ( & res)
608+ . and_then ( |c| c. name ( "contact_user" ) )
609+ . map ( |m| m. as_str ( ) . trim ( ) . to_string ( ) )
610+ . ok_or_else ( || {
611+ error ! ( "{}" , res) ;
612+ anyhow:: anyhow!( "Could not extract user contact" )
613+ } ) ?;
614+
615+ Ok ( contact_user)
582616}
583617
584618#[ cfg( test) ]
0 commit comments