@@ -11,6 +11,7 @@ const http = require('http'),
1111 events = require ( 'events' ) ,
1212 url = require ( 'url' ) ,
1313 util = require ( 'util' ) ,
14+ splitargs = require ( 'splitargs' ) ,
1415 linerase = require ( './utils' ) . linerase ,
1516 parseSOAPString = require ( './utils' ) . parseSOAPString ,
1617 parseString = require ( 'xml2js' ) . parseString ,
@@ -383,8 +384,10 @@ Cam.prototype._requestPart2 = function(options, callback) {
383384Cam . prototype . _parseChallenge = function ( digest ) {
384385 const prefix = 'Digest ' ;
385386 const challenge = digest . substring ( digest . indexOf ( prefix ) + prefix . length ) ;
386- const parts = challenge . split ( ',' )
387- . map ( part => part . match ( / ^ \s * ?( [ a - z A - Z 0 - 9 ] + ) = " ? ( [ ^ " ] * ) " ? \s * ?$ / ) . slice ( 1 ) ) ;
387+ // use splitargs to handle things like qop="auth,auth-int"
388+ let parts = splitargs ( challenge , ',' , true ) ; // get a list of Key=Value items. Whitespace will be consumed in the RegEx with \s before the RexEx Groups
389+ // split into Key-Value pairs. We can split on '=' as this cannot appear in the Key name, replacing String with an Array in 'parts'
390+ parts = parts . map ( part => part . match ( / ^ \s * ?( [ a - z A - Z 0 - 9 ] + ) = " ? ( [ ^ " ] * ) " ? \s * ?$ / ) . slice ( 1 ) ) ;
388391 return Object . fromEntries ( parts ) ;
389392} ;
390393
@@ -400,48 +403,62 @@ Cam.prototype.digestAuth = function(wwwAuthenticate, reqOptions) {
400403 const challenge = this . _parseChallenge ( wwwAuthenticate ) ;
401404 const ha1 = crypto . createHash ( 'md5' ) ;
402405 ha1 . update ( [ this . username , challenge . realm , this . password ] . join ( ':' ) ) ;
406+
407+ // Sony SRG-XP1 sends qop="auth,auth-int" it means the Server will accept either "auth" or "auth-int". We select "auth"
408+ if ( typeof challenge . qop === 'string' && challenge . qop === 'auth,auth-int' ) challenge . qop = 'auth' ;
409+
403410 const ha2 = crypto . createHash ( 'md5' ) ;
404411 ha2 . update ( [ reqOptions . method , reqOptions . path ] . join ( ':' ) ) ;
405412
406413 let cnonce = null ;
407414 let nc = null ;
408- if ( typeof challenge . qop === 'string' ) {
415+ if ( typeof challenge . qop === 'string' && challenge . qop === 'auth' ) {
409416 const cnonceHash = crypto . createHash ( 'md5' ) ;
410417 cnonceHash . update ( Math . random ( ) . toString ( 36 ) ) ;
411418 cnonce = cnonceHash . digest ( 'hex' ) . substring ( 0 , 8 ) ;
412419 nc = this . updateNC ( ) ;
413420 }
414421
422+ // No qop -> Response = MD5(HA1:nonce:HA2);
423+ // With qop -> Response = MD5(HA1:nonce:nonceCount:cnonce:qop:HA2)
415424 const response = crypto . createHash ( 'md5' ) ;
416425 const responseParams = [
417426 ha1 . digest ( 'hex' ) ,
418427 challenge . nonce
419428 ] ;
420- if ( cnonce ) {
429+ if ( cnonce != null ) {
421430 responseParams . push ( nc ) ;
422431 responseParams . push ( cnonce ) ;
432+ responseParams . push ( challenge . qop ) ;
423433 }
424434
425- responseParams . push ( challenge . qop ) ;
426435 responseParams . push ( ha2 . digest ( 'hex' ) ) ;
427436 response . update ( responseParams . join ( ':' ) ) ;
428437
429438 const authParams = {
430- username : this . username ,
431- realm : challenge . realm ,
432- nonce : challenge . nonce ,
433- uri : reqOptions . path ,
434- qop : challenge . qop ,
435- response : response . digest ( 'hex' ) ,
439+ username : `"${ this . username } "` ,
440+ realm : `"${ challenge . realm } "` ,
441+ nonce : `"${ challenge . nonce } "` ,
442+ uri : `"${ reqOptions . path } "`
436443 } ;
437- if ( challenge . opaque ) {
438- authParams . opaque = challenge . opaque ;
444+
445+ // RFC says only send qop, nc and cnonce if there was a QOP in the Header
446+ // 'qop' and 'nc' do not have quotes around the Values
447+ if ( 'qop' in challenge ) {
448+ authParams . qop = challenge . qop ; // no quotes
449+ authParams . nc = nc ; // no quotes
450+ authParams . cnonce = `"${ cnonce } "` ;
439451 }
440- if ( cnonce ) {
441- authParams . nc = nc ;
442- authParams . cnonce = cnonce ;
452+
453+ authParams . response = `"${ response . digest ( 'hex' ) } "` ;
454+
455+ if ( challenge . opaque ) {
456+ authParams . opaque = `"${ challenge . opaque } "` ;
443457 }
444- return 'Digest ' + Object . entries ( authParams ) . map ( ( [ key , value ] ) => `${ key } ="${ value } "` ) . join ( ',' ) ;
458+
459+ // There are RFC non compliances here. Some values do not need to be in quotes for example 'nc'
460+ const result = 'Digest ' + Object . entries ( authParams ) . map ( ( [ key , value ] ) => `${ key } =${ value } ` ) . join ( ',' ) ;
461+ return result ;
445462} ;
446463
447464/**
0 commit comments