2016-06-22 22:14:48 +02:00
/ *
2023-03-15 23:02:31 +01:00
* Copyright ( C ) 2016 - 2023 Jones Magloire @ Joxit
2016-06-22 22:14:48 +02:00
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2021-03-03 00:05:44 +01:00
export class Http {
2021-04-08 18:11:08 +02:00
constructor ( opts ) {
2021-03-03 00:05:44 +01:00
this . oReq = new XMLHttpRequest ( ) ;
this . oReq . hasHeader = hasHeader ;
this . oReq . getErrorMessage = getErrorMessage ;
this . _events = { } ;
this . _headers = { } ;
2021-04-08 18:11:08 +02:00
this . onAuthentication = opts && opts . onAuthentication ;
this . withCredentials = opts && opts . withCredentials ;
2021-03-03 00:05:44 +01:00
}
getContentDigest ( cb ) {
if ( this . oReq . hasHeader ( 'Docker-Content-Digest' ) ) {
// Same origin or advanced CORS headers set:
// 'Access-Control-Expose-Headers: Docker-Content-Digest'
cb ( this . oReq . getResponseHeader ( 'Docker-Content-Digest' ) ) ;
} else if ( window . crypto && window . TextEncoder ) {
crypto . subtle . digest ( 'SHA-256' , new TextEncoder ( ) . encode ( this . oReq . responseText ) ) . then ( function ( buffer ) {
cb (
'sha256:' +
Array . from ( new Uint8Array ( buffer ) )
2021-04-08 18:11:08 +02:00
. map ( ( byte ) => byte . toString ( 16 ) . padStart ( 2 , '0' ) )
2021-03-03 00:05:44 +01:00
. join ( '' )
) ;
} ) ;
} else {
// IE and old Edge
// simply do not call the callback and skip the setup downstream
}
2019-09-02 18:16:07 +02:00
}
2021-03-03 00:05:44 +01:00
addEventListener ( e , f ) {
this . _events [ e ] = f ;
const self = this ;
switch ( e ) {
case 'loadend' : {
self . oReq . addEventListener ( 'loadend' , function ( ) {
2022-04-09 00:02:52 +02:00
if ( this . status === 401 && ! this . withCredentials ) {
2021-09-13 09:14:00 +02:00
const tokenAuth =
this . hasHeader ( 'www-authenticate' ) && parseAuthenticateHeader ( this . getResponseHeader ( 'www-authenticate' ) ) ;
2021-04-08 18:11:08 +02:00
self . onAuthentication ( tokenAuth , ( bearer ) => {
const req = new XMLHttpRequest ( ) ;
req . _url = self . _url ;
req . open ( self . _method , self . _url ) ;
for ( let key in self . _events ) {
req . addEventListener ( key , self . _events [ key ] ) ;
}
for ( let key in self . _headers ) {
req . setRequestHeader ( key , self . _headers [ key ] ) ;
}
if ( bearer && bearer . token ) {
2021-09-13 09:14:00 +02:00
req . setRequestHeader ( 'Authorization' , ` Bearer ${ bearer . token } ` ) ;
2021-04-08 18:11:08 +02:00
} else {
req . withCredentials = true ;
}
req . hasHeader = hasHeader ;
2023-02-01 21:54:18 +01:00
req . getErrorMessage = getErrorMessage ;
2021-04-08 18:11:08 +02:00
self . oReq = req ;
req . send ( ) ;
} ) ;
2016-06-22 22:14:48 +02:00
} else {
2016-07-06 18:40:49 +02:00
f . bind ( this ) ( ) ;
2016-06-22 22:14:48 +02:00
}
} ) ;
break ;
}
2021-03-03 00:05:44 +01:00
case 'load' : {
self . oReq . addEventListener ( 'load' , function ( ) {
2017-06-11 23:04:15 +02:00
if ( this . status !== 401 ) {
f . bind ( this ) ( ) ;
}
} ) ;
break ;
}
2021-03-03 00:05:44 +01:00
default : {
self . oReq . addEventListener ( e , function ( ) {
2016-07-06 18:40:49 +02:00
f . bind ( this ) ( ) ;
2016-06-22 22:14:48 +02:00
} ) ;
break ;
}
2021-03-03 00:05:44 +01:00
}
2016-06-22 22:14:48 +02:00
}
2021-03-03 00:05:44 +01:00
setRequestHeader ( header , value ) {
this . oReq . setRequestHeader ( header , value ) ;
this . _headers [ header ] = value ;
}
2016-07-14 23:50:43 +02:00
2021-03-03 00:05:44 +01:00
open ( m , u ) {
this . _method = m ;
this . _url = u ;
2021-03-26 00:04:28 +01:00
this . oReq . _url = u ;
2021-04-08 18:11:08 +02:00
if ( this . withCredentials ) {
this . oReq . withCredentials = true ;
}
2021-03-03 00:05:44 +01:00
this . oReq . open ( m , u ) ;
}
2016-06-22 22:14:48 +02:00
2021-03-03 00:05:44 +01:00
send ( ) {
this . oReq . send ( ) ;
}
}
2017-08-06 11:39:01 +02:00
2021-03-03 00:05:44 +01:00
const hasHeader = function ( header ) {
return this . getAllResponseHeaders ( )
. split ( '\n' )
. some ( function ( h ) {
return new RegExp ( '^' + header + ':' , 'i' ) . test ( h ) ;
} ) ;
2018-05-10 23:36:39 +02:00
} ;
2021-03-03 00:05:44 +01:00
const getErrorMessage = function ( ) {
2021-03-26 00:04:28 +01:00
if ( this . _url . match ( '^http://' ) && window . location . protocol === 'https:' ) {
2023-02-01 21:54:18 +01:00
return { code : 'MIXED_CONTENT' , url : this . _url } ;
2021-03-26 00:04:28 +01:00
} else if ( ! this . _url || ! this . _url . match ( '^http' ) ) {
2023-02-01 21:54:18 +01:00
return { code : 'INCORRECT_URL' , url : this . _url } ;
2018-07-15 20:56:16 +02:00
} else if ( this . withCredentials && ! this . hasHeader ( 'Access-Control-Allow-Credentials' ) ) {
2021-03-03 00:05:44 +01:00
return (
"The `Access-Control-Allow-Credentials` header in the response is missing and must be set to `true` when the request's credentials mode is on. Origin `" +
2021-03-26 00:04:28 +01:00
new URL ( this . _url ) . origin +
2021-03-03 00:05:44 +01:00
'` is therefore not allowed access.'
) ;
2018-05-10 23:36:39 +02:00
}
2021-03-03 00:05:44 +01:00
return (
'An error occured: Check your connection and your registry must have `Access-Control-Allow-Origin` header set to `' +
window . location . origin +
'`'
) ;
} ;
2021-04-08 18:11:08 +02:00
const AUTHENTICATE _HEADER _REGEX = /Bearer realm="(?<realm>[^"]+)",service="(?<service>[^"]+)",scope="(?<scope>[^"]+)"/ ;
const parseAuthenticateHeader = ( header ) => {
const exec = AUTHENTICATE _HEADER _REGEX . exec ( header ) ;
return exec && exec . groups ;
} ;