17
17
package com .ning .http .client ;
18
18
19
19
import static com .ning .http .util .MiscUtils .isNonEmpty ;
20
- import static java .nio .charset .StandardCharsets .ISO_8859_1 ;
21
- import static java .nio .charset .StandardCharsets .UTF_8 ;
20
+ import static java .nio .charset .StandardCharsets .*;
22
21
23
22
import com .ning .http .client .uri .Uri ;
23
+ import com .ning .http .util .AuthenticatorUtils ;
24
24
import com .ning .http .util .StringUtils ;
25
25
26
26
import java .nio .charset .Charset ;
33
33
*/
34
34
public class Realm {
35
35
36
+ private static final String EMPTY_ENTITY_MD5 = "d41d8cd98f00b204e9800998ecf8427e" ;
36
37
private static final String DEFAULT_NC = "00000001" ;
37
38
38
39
private final String principal ;
@@ -274,24 +275,24 @@ public static class RealmBuilder {
274
275
// This code is already Apache licenced.
275
276
//
276
277
277
- private String principal = "" ;
278
- private String password = "" ;
278
+ private String principal ;
279
+ private String password ;
279
280
private AuthScheme scheme = AuthScheme .NONE ;
280
- private String realmName = "" ;
281
- private String nonce = "" ;
282
- private String algorithm = "MD5" ;
283
- private String response = "" ;
284
- private String opaque = "" ;
285
- private String qop = "auth" ;
281
+ private String realmName ;
282
+ private String nonce ;
283
+ private String algorithm ;
284
+ private String response ;
285
+ private String opaque ;
286
+ private String qop ;
286
287
private String nc = DEFAULT_NC ;
287
- private String cnonce = "" ;
288
+ private String cnonce ;
288
289
private Uri uri ;
289
290
private String methodName = "GET" ;
290
291
private boolean usePreemptive = false ;
291
292
private String ntlmDomain = System .getProperty ("http.auth.ntlm.domain" , "" );
292
293
private Charset charset = UTF_8 ;
293
294
private String ntlmHost = "localhost" ;
294
- private boolean useAbsoluteURI = true ;
295
+ private boolean useAbsoluteURI = false ;
295
296
private boolean omitQuery = false ;
296
297
private boolean targetProxy = false ;
297
298
@@ -402,7 +403,9 @@ public String getQop() {
402
403
}
403
404
404
405
public RealmBuilder setQop (String qop ) {
405
- this .qop = qop ;
406
+ if (isNonEmpty (qop )) {
407
+ this .qop = qop ;
408
+ }
406
409
return this ;
407
410
}
408
411
@@ -469,6 +472,27 @@ public RealmBuilder setTargetProxy(boolean targetProxy) {
469
472
return this ;
470
473
}
471
474
475
+ private String parseRawQop (String rawQop ) {
476
+ String [] rawServerSupportedQops = rawQop .split ("," );
477
+ String [] serverSupportedQops = new String [rawServerSupportedQops .length ];
478
+ for (int i = 0 ; i < rawServerSupportedQops .length ; i ++) {
479
+ serverSupportedQops [i ] = rawServerSupportedQops [i ].trim ();
480
+ }
481
+
482
+ // prefer auth over auth-int
483
+ for (String rawServerSupportedQop : serverSupportedQops ) {
484
+ if (rawServerSupportedQop .equals ("auth" ))
485
+ return rawServerSupportedQop ;
486
+ }
487
+
488
+ for (String rawServerSupportedQop : serverSupportedQops ) {
489
+ if (rawServerSupportedQop .equals ("auth-int" ))
490
+ return rawServerSupportedQop ;
491
+ }
492
+
493
+ return null ;
494
+ }
495
+
472
496
public RealmBuilder parseWWWAuthenticateHeader (String headerLine ) {
473
497
setRealmName (match (headerLine , "realm" ));
474
498
setNonce (match (headerLine , "nonce" ));
@@ -477,7 +501,12 @@ public RealmBuilder parseWWWAuthenticateHeader(String headerLine) {
477
501
setAlgorithm (algorithm );
478
502
}
479
503
setOpaque (match (headerLine , "opaque" ));
480
- setQop (match (headerLine , "qop" ));
504
+
505
+ String rawQop = match (headerLine , "qop" );
506
+ if (rawQop != null ) {
507
+ setQop (parseRawQop (rawQop ));
508
+ }
509
+
481
510
if (isNonEmpty (getNonce ())) {
482
511
setScheme (AuthScheme .DIGEST );
483
512
} else {
@@ -490,6 +519,7 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) {
490
519
setRealmName (match (headerLine , "realm" ));
491
520
setNonce (match (headerLine , "nonce" ));
492
521
setOpaque (match (headerLine , "opaque" ));
522
+ // FIXME what about algorithm and opaque?
493
523
setQop (match (headerLine , "qop" ));
494
524
if (isNonEmpty (getNonce ())) {
495
525
setScheme (AuthScheme .DIGEST );
@@ -501,25 +531,24 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) {
501
531
}
502
532
503
533
public RealmBuilder clone (Realm clone ) {
504
- setRealmName (clone .getRealmName ());
505
- setAlgorithm (clone .getAlgorithm ());
506
- setMethodName (clone .getMethodName ());
507
- setNc (clone .getNc ());
508
- setNonce (clone .getNonce ());
509
- setPassword (clone .getPassword ());
510
- setPrincipal (clone .getPrincipal ());
511
- setCharset (clone .getCharset ());
512
- setOpaque (clone .getOpaque ());
513
- setQop (clone .getQop ());
514
- setScheme (clone .getScheme ());
515
- setUri (clone .getUri ());
516
- setUsePreemptiveAuth (clone .getUsePreemptiveAuth ());
517
- setNtlmDomain (clone .getNtlmDomain ());
518
- setNtlmHost (clone .getNtlmHost ());
519
- setUseAbsoluteURI (clone .isUseAbsoluteURI ());
520
- setOmitQuery (clone .isOmitQuery ());
521
- setTargetProxy (clone .isTargetProxy ());
522
- return this ;
534
+ return setRealmName (clone .getRealmName ())//
535
+ .setAlgorithm (clone .getAlgorithm ())//
536
+ .setMethodName (clone .getMethodName ())//
537
+ .setNc (clone .getNc ())//
538
+ .setNonce (clone .getNonce ())//
539
+ .setPassword (clone .getPassword ())//
540
+ .setPrincipal (clone .getPrincipal ())//
541
+ .setCharset (clone .getCharset ())//
542
+ .setOpaque (clone .getOpaque ())//
543
+ .setQop (clone .getQop ())//
544
+ .setScheme (clone .getScheme ())//
545
+ .setUri (clone .getUri ())//
546
+ .setUsePreemptiveAuth (clone .getUsePreemptiveAuth ())//
547
+ .setNtlmDomain (clone .getNtlmDomain ())//
548
+ .setNtlmHost (clone .getNtlmHost ())//
549
+ .setUseAbsoluteURI (clone .isUseAbsoluteURI ())//
550
+ .setOmitQuery (clone .isOmitQuery ())//
551
+ .setTargetProxy (clone .isTargetProxy ());
523
552
}
524
553
525
554
private void newCnonce (MessageDigest md ) {
@@ -534,11 +563,11 @@ private void newCnonce(MessageDigest md) {
534
563
*/
535
564
private String match (String headerLine , String token ) {
536
565
if (headerLine == null ) {
537
- return "" ;
566
+ return null ;
538
567
}
539
568
540
569
int match = headerLine .indexOf (token );
541
- if (match <= 0 ) return "" ;
570
+ if (match <= 0 ) return null ;
542
571
543
572
// = to skip
544
573
match += token .length () + 1 ;
@@ -557,46 +586,64 @@ public RealmBuilder setCharset(Charset charset) {
557
586
return this ;
558
587
}
559
588
560
- private void newResponse (MessageDigest md ) {
561
- // BEWARE: compute first as it used the cached StringBuilder
562
- String url = uri .toUrl ();
563
-
564
- StringBuilder sb = StringUtils .stringBuilder ();
565
- sb .append (principal )
566
- .append (":" )
567
- .append (realmName )
568
- .append (":" )
569
- .append (password );
589
+ private byte [] md5FromRecycledStringBuilder (StringBuilder sb , MessageDigest md ) {
570
590
md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
571
591
sb .setLength (0 );
572
- byte [] ha1 = md .digest ();
592
+ return md .digest ();
593
+ }
594
+
595
+ private byte [] secretDigest (StringBuilder sb , MessageDigest md ) {
573
596
574
- sb .append (methodName )
575
- .append (':' )
576
- .append (url );
597
+ sb .append (principal ).append (':' ).append (realmName ).append (':' ).append (password );
598
+ byte [] ha1 = md5FromRecycledStringBuilder (sb , md );
599
+
600
+ if (algorithm == null || algorithm .equals ("MD5" )) {
601
+ return ha1 ;
602
+ } else if ("MD5-sess" .equals (algorithm )) {
603
+ appendBase16 (sb , ha1 );
604
+ sb .append (':' ).append (nonce ).append (':' ).append (cnonce );
605
+ return md5FromRecycledStringBuilder (sb , md );
606
+ }
607
+
608
+ throw new UnsupportedOperationException ("Digest algorithm not supported: " + algorithm );
609
+ }
610
+
611
+ private byte [] dataDigest (StringBuilder sb , String digestUri , MessageDigest md ) {
577
612
578
- md . update ( StringUtils . charSequence2ByteBuffer ( sb , ISO_8859_1 ) );
579
- sb . setLength ( 0 );
580
- byte [] ha2 = md . digest ( );
613
+ sb . append ( methodName ). append ( ':' ). append ( digestUri );
614
+ if ( "auth-int" . equals ( qop )) {
615
+ sb . append ( ':' ). append ( EMPTY_ENTITY_MD5 );
581
616
582
- appendBase16 (sb , ha1 );
583
- sb .append (':' ).append (nonce ).append (':' );
617
+ } else if (qop != null && !qop .equals ("auth" )) {
618
+ throw new UnsupportedOperationException ("Digest qop not supported: " + qop );
619
+ }
584
620
585
- if (isNonEmpty (qop )) {
586
- //qop ="auth" or "auth-int"
587
- sb .append (nc )//
588
- .append (':' )//
589
- .append (cnonce )//
590
- .append (':' )//
591
- .append (qop )//
592
- .append (':' );
621
+ return md5FromRecycledStringBuilder (sb , md );
622
+ }
623
+
624
+ private void appendDataBase (StringBuilder sb ) {
625
+ sb .append (':' ).append (nonce ).append (':' );
626
+ if ("auth" .equals (qop ) || "auth-int" .equals (qop )) {
627
+ sb .append (nc ).append (':' ).append (cnonce ).append (':' ).append (qop ).append (':' );
593
628
}
594
-
595
- appendBase16 (sb , ha2 );
596
- md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
597
- sb .setLength (0 );
598
- byte [] digest = md .digest ();
599
- response = toHexString (digest );
629
+ }
630
+
631
+ private void newResponse (MessageDigest md ) {
632
+ // BEWARE: compute first as it used the cached StringBuilder
633
+ String digestUri = AuthenticatorUtils .computeRealmURI (uri , useAbsoluteURI , omitQuery );
634
+
635
+ StringBuilder sb = StringUtils .stringBuilder ();
636
+
637
+ // WARNING: DON'T MOVE, BUFFER IS RECYCLED!!!!
638
+ byte [] secretDigest = secretDigest (sb , md );
639
+ byte [] dataDigest = dataDigest (sb , digestUri , md );
640
+
641
+ appendBase16 (sb , secretDigest );
642
+ appendDataBase (sb );
643
+ appendBase16 (sb , dataDigest );
644
+
645
+ byte [] responseDigest = md5FromRecycledStringBuilder (sb , md );
646
+ response = toHexString (responseDigest );
600
647
}
601
648
602
649
private static String toHexString (byte [] data ) {
0 commit comments