"use strict"; const cl = global.botanLoader; const Dragonfly = global.Dragonfly; const Cookie = cl.load( "botanss.net.components.Cookie" ); class ContentSecurityPolicy { constructor() { this.sources = {}; } any() { return 0 < Object.keys( this.sources ).length; } add( src, scope ) { this.sources[ src ] ||= {}; this._add( this.sources[ src ], scope, src ); } _add( s, scope, _name ) { if( scope.startsWith( "'nonce-" ) && "'unsafe-inline'" in s ) { Dragonfly.Warning( `Removing 'unsafe-inline' from ${_name} for ${scope}` ); delete s[ "'unsafe-inline'" ]; } s[ scope ] = true; } merge( cspStr ) { for( let src of cspStr.split( ";" ) ) { src = src.trim(); if( !src ) continue; var d = src.indexOf( " " ); var name = src.substr( 0, d ); this.sources[ name ] ||= {}; for( let val of src.substr( d + 1 ).split( " " ) ) { this.sources[ name ][ val ] = true; } } } toString() { var s = ""; for( let name in this.sources ) { if( s ) s += " "; s += `${name} ${Object.keys( this.sources[ name ] ).join( " " )};`; } return s; } } class CResponse { constructor( res, Http ) { this.raw = res; this.canExit = true; this.statusCode = 200; this.contentSecurityPolicy = new ContentSecurityPolicy(); this.headers = { "Content-Type": "text/html; charset=utf-8" , "Powered-By": "Botanical Framework (Node.js)" , "Content-Security-Policy": this.contentSecurityPolicy }; this.content = ""; this.cookie = new Cookie( "", Http ); } mergeHeader( key, value ) { switch( key ) { case "Content-Security-Policy": this.headers[ key ] = this.headers[ key ] + ' ' + value; break; default: throw new Error( `Merge header not implemented: ${key}` ); } } end() { if( this.canExit ) { this.canExit = false; this.raw.writeHead( this.statusCode, this.headers ); this.raw.end( this.content ); if( this.raw._rejection ) { process.removeListener( "unhandledRejection", this.raw._rejection ); } if( this.raw._uncaught ) { process.removeListener( "uncaughtException", this.raw._uncaught ); } } } write( str ) { this.content = str } writeLine( str ) { this.content += str + "\n"; } } class CRequest { get isPost() { return this.raw.method == 'POST'; } get remoteAddr() { return this.raw.socket.remoteAddress; } compat_parse_url( url, base = "http://example.invalid" ) { var u = new URL( url, base ); var isRelative = !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url); return { protocol: isRelative ? null : u.protocol, slashes: isRelative ? null : true, auth: u.username || u.password ? `${decodeURIComponent(u.username)}:${decodeURIComponent(u.password)}` : null, host: isRelative ? null : u.host, port: isRelative ? null : (u.port || null), hostname: isRelative ? null : u.hostname, hash: u.hash || null, search: u.search || null, query: u.search ? u.search.slice(1) : null, pathname: u.pathname, path: u.pathname + u.search, href: isRelative ? u.pathname + u.search + u.hash : u.href, }; } constructor( req, Http ) { this.raw = req; this.headers = req.headers; this.uri = this.compat_parse_url( req.url ); this.cookie = new Cookie( req.headers.cookie, Http ); } } class Http { constructor( req, res ) { // Simple Http Model this.response = new CResponse( res, this ); this.request = new CRequest( req, this ); } } module.exports = Http;