2626import java .util .concurrent .ThreadLocalRandom ;
2727
2828import org .asynchttpclient .uri .Uri ;
29+ import org .asynchttpclient .util .AuthenticatorUtils ;
2930import org .asynchttpclient .util .StringUtils ;
3031
3132/**
3435public class Realm {
3536
3637 private static final String DEFAULT_NC = "00000001" ;
38+ private static final String EMPTY_ENTITY_MD5 = "d41d8cd98f00b204e9800998ecf8427e" ;
3739
3840 private final String principal ;
3941 private final String password ;
@@ -251,24 +253,24 @@ public static class RealmBuilder {
251253 // This code is already Apache licenced.
252254 //
253255
254- private String principal = "" ;
255- private String password = "" ;
256+ private String principal ;
257+ private String password ;
256258 private AuthScheme scheme = AuthScheme .NONE ;
257- private String realmName = "" ;
258- private String nonce = "" ;
259- private String algorithm = "MD5" ;
260- private String response = "" ;
261- private String opaque = "" ;
262- private String qop = "auth" ;
259+ private String realmName ;
260+ private String nonce ;
261+ private String algorithm ;
262+ private String response ;
263+ private String opaque ;
264+ private String qop ;
263265 private String nc = DEFAULT_NC ;
264- private String cnonce = "" ;
266+ private String cnonce ;
265267 private Uri uri ;
266268 private String methodName = "GET" ;
267269 private boolean usePreemptive ;
268270 private String ntlmDomain = System .getProperty ("http.auth.ntlm.domain" , "" );
269271 private Charset charset = UTF_8 ;
270272 private String ntlmHost = "localhost" ;
271- private boolean useAbsoluteURI = true ;
273+ private boolean useAbsoluteURI = false ;
272274 private boolean omitQuery ;
273275 private boolean targetProxy ;
274276
@@ -378,7 +380,9 @@ public String getQop() {
378380 }
379381
380382 public RealmBuilder setQop (String qop ) {
381- this .qop = qop ;
383+ if (isNonEmpty (qop )) {
384+ this .qop = qop ;
385+ }
382386 return this ;
383387 }
384388
@@ -444,6 +448,26 @@ public RealmBuilder setTargetProxy(boolean targetProxy) {
444448 this .targetProxy = targetProxy ;
445449 return this ;
446450 }
451+ private String parseRawQop (String rawQop ) {
452+ String [] rawServerSupportedQops = rawQop .split ("," );
453+ String [] serverSupportedQops = new String [rawServerSupportedQops .length ];
454+ for (int i = 0 ; i < rawServerSupportedQops .length ; i ++) {
455+ serverSupportedQops [i ] = rawServerSupportedQops [i ].trim ();
456+ }
457+
458+ // prefer auth over auth-int
459+ for (String rawServerSupportedQop : serverSupportedQops ) {
460+ if (rawServerSupportedQop .equals ("auth" ))
461+ return rawServerSupportedQop ;
462+ }
463+
464+ for (String rawServerSupportedQop : serverSupportedQops ) {
465+ if (rawServerSupportedQop .equals ("auth-int" ))
466+ return rawServerSupportedQop ;
467+ }
468+
469+ return null ;
470+ }
447471
448472 public RealmBuilder parseWWWAuthenticateHeader (String headerLine ) {
449473 setRealmName (match (headerLine , "realm" ));
@@ -453,7 +477,12 @@ public RealmBuilder parseWWWAuthenticateHeader(String headerLine) {
453477 setAlgorithm (algorithm );
454478 }
455479 setOpaque (match (headerLine , "opaque" ));
456- setQop (match (headerLine , "qop" ));
480+
481+ String rawQop = match (headerLine , "qop" );
482+ if (rawQop != null ) {
483+ setQop (parseRawQop (rawQop ));
484+ }
485+
457486 if (isNonEmpty (getNonce ())) {
458487 setScheme (AuthScheme .DIGEST );
459488 } else {
@@ -466,6 +495,10 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) {
466495 setRealmName (match (headerLine , "realm" ));
467496 setNonce (match (headerLine , "nonce" ));
468497 setOpaque (match (headerLine , "opaque" ));
498+ String algorithm = match (headerLine , "algorithm" );
499+ if (isNonEmpty (algorithm )) {
500+ setAlgorithm (algorithm );
501+ }
469502 setQop (match (headerLine , "qop" ));
470503 if (isNonEmpty (getNonce ())) {
471504 setScheme (AuthScheme .DIGEST );
@@ -477,25 +510,24 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) {
477510 }
478511
479512 public RealmBuilder clone (Realm clone ) {
480- setRealmName (clone .getRealmName ());
481- setAlgorithm (clone .getAlgorithm ());
482- setMethodName (clone .getMethodName ());
483- setNc (clone .getNc ());
484- setNonce (clone .getNonce ());
485- setPassword (clone .getPassword ());
486- setPrincipal (clone .getPrincipal ());
487- setCharset (clone .getCharset ());
488- setOpaque (clone .getOpaque ());
489- setQop (clone .getQop ());
490- setScheme (clone .getScheme ());
491- setUri (clone .getUri ());
492- setUsePreemptiveAuth (clone .getUsePreemptiveAuth ());
493- setNtlmDomain (clone .getNtlmDomain ());
494- setNtlmHost (clone .getNtlmHost ());
495- setUseAbsoluteURI (clone .isUseAbsoluteURI ());
496- setOmitQuery (clone .isOmitQuery ());
497- setTargetProxy (clone .isTargetProxy ());
498- return this ;
513+ return setRealmName (clone .getRealmName ())//
514+ .setAlgorithm (clone .getAlgorithm ())//
515+ .setMethodName (clone .getMethodName ())//
516+ .setNc (clone .getNc ())//
517+ .setNonce (clone .getNonce ())//
518+ .setPassword (clone .getPassword ())//
519+ .setPrincipal (clone .getPrincipal ())//
520+ .setCharset (clone .getCharset ())//
521+ .setOpaque (clone .getOpaque ())//
522+ .setQop (clone .getQop ())//
523+ .setScheme (clone .getScheme ())//
524+ .setUri (clone .getUri ())//
525+ .setUsePreemptiveAuth (clone .getUsePreemptiveAuth ())//
526+ .setNtlmDomain (clone .getNtlmDomain ())//
527+ .setNtlmHost (clone .getNtlmHost ())//
528+ .setUseAbsoluteURI (clone .isUseAbsoluteURI ())//
529+ .setOmitQuery (clone .isOmitQuery ())//
530+ .setTargetProxy (clone .isTargetProxy ());
499531 }
500532
501533 private void newCnonce (MessageDigest md ) {
@@ -510,12 +542,12 @@ private void newCnonce(MessageDigest md) {
510542 */
511543 private String match (String headerLine , String token ) {
512544 if (headerLine == null ) {
513- return "" ;
545+ return null ;
514546 }
515547
516548 int match = headerLine .indexOf (token );
517549 if (match <= 0 )
518- return "" ;
550+ return null ;
519551
520552 // = to skip
521553 match += token .length () + 1 ;
@@ -534,48 +566,64 @@ public RealmBuilder setCharset(Charset charset) {
534566 return this ;
535567 }
536568
537- private void newResponse (MessageDigest md ) {
538- // BEWARE: compute first as it used the cached StringBuilder
539- String url = uri .toUrl ();
540-
541- StringBuilder sb = StringUtils .stringBuilder ();
542- sb .append (principal )
543- .append (":" )
544- .append (realmName )
545- .append (":" )
546- .append (password );
569+ private byte [] md5FromRecycledStringBuilder (StringBuilder sb , MessageDigest md ) {
547570 md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
548571 sb .setLength (0 );
549- byte [] ha1 = md .digest ();
572+ return md .digest ();
573+ }
574+
575+ private byte [] secretDigest (StringBuilder sb , MessageDigest md ) {
576+
577+ sb .append (principal ).append (':' ).append (realmName ).append (':' ).append (password );
578+ byte [] ha1 = md5FromRecycledStringBuilder (sb , md );
579+
580+ if (algorithm == null || algorithm .equals ("MD5" )) {
581+ return ha1 ;
582+ } else if ("MD5-sess" .equals (algorithm )) {
583+ appendBase16 (sb , ha1 );
584+ sb .append (':' ).append (nonce ).append (':' ).append (cnonce );
585+ return md5FromRecycledStringBuilder (sb , md );
586+ }
550587
551- //HA2 if qop is auth-int is methodName:url:md5(entityBody)
552- sb .append (methodName )
553- .append (':' )
554- .append (url );
588+ throw new UnsupportedOperationException ("Digest algorithm not supported: " + algorithm );
589+ }
555590
556- md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
557- sb .setLength (0 );
558- byte [] ha2 = md .digest ();
591+ private byte [] dataDigest (StringBuilder sb , String digestUri , MessageDigest md ) {
592+
593+ sb .append (methodName ).append (':' ).append (digestUri );
594+ if ("auth-int" .equals (qop )) {
595+ sb .append (':' ).append (EMPTY_ENTITY_MD5 );
559596
560- appendBase16 (sb , ha1 );
561- sb .append (':' ).append (nonce ).append (':' );
597+ } else if (qop != null && !qop .equals ("auth" )) {
598+ throw new UnsupportedOperationException ("Digest qop not supported: " + qop );
599+ }
562600
563- if (isNonEmpty (qop )) {
564- //qop ="auth" or "auth-int"
565- sb .append (nc )//
566- .append (':' )//
567- .append (cnonce )//
568- .append (':' )//
569- .append (qop )//
570- .append (':' );
601+ return md5FromRecycledStringBuilder (sb , md );
602+ }
603+
604+ private void appendDataBase (StringBuilder sb ) {
605+ sb .append (':' ).append (nonce ).append (':' );
606+ if ("auth" .equals (qop ) || "auth-int" .equals (qop )) {
607+ sb .append (nc ).append (':' ).append (cnonce ).append (':' ).append (qop ).append (':' );
571608 }
572-
573- appendBase16 (sb , ha2 );
574- md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
575- sb .setLength (0 );
576- byte [] digest = md .digest ();
577-
578- response = toHexString (digest );
609+ }
610+
611+ private void newResponse (MessageDigest md ) {
612+ // BEWARE: compute first as it used the cached StringBuilder
613+ String digestUri = AuthenticatorUtils .computeRealmURI (uri , useAbsoluteURI , omitQuery );
614+
615+ StringBuilder sb = StringUtils .stringBuilder ();
616+
617+ // WARNING: DON'T MOVE, BUFFER IS RECYCLED!!!!
618+ byte [] secretDigest = secretDigest (sb , md );
619+ byte [] dataDigest = dataDigest (sb , digestUri , md );
620+
621+ appendBase16 (sb , secretDigest );
622+ appendDataBase (sb );
623+ appendBase16 (sb , dataDigest );
624+
625+ byte [] responseDigest = md5FromRecycledStringBuilder (sb , md );
626+ response = toHexString (responseDigest );
579627 }
580628
581629 private static String toHexString (byte [] data ) {
0 commit comments