import { BufferUtil } from "./buffer-util" ; export class HTTPBuffer { private _rawContent : Buffer = BufferUtil . fromString ( "" ) ; private _complete : boolean = false ; private _chunks : Array < HTTPChunk > ; private _headers : { [ id ] : string ; } ; private _method : string ; private _rawBody : Buffer ; private _requestLine : string ; private _statusCode : number ; private _statusLine : string ; private _uri : string ; public static errorResponse ( message ) { const buffer = new HTTPBuffer ( ) ; const payload = "HTTP/1.1 500 Error\r\nContent-Type: text/plain\r\n" + "Content-Length: " + message . length + "\r\n\r\n" + message ; buffer . append ( Buffer . from ( payload ) ) ; return buffer ; } public append ( data ) { this . _rawContent = Buffer . concat ( [ this . _rawContent , data ] ) ; if ( this . _headers === undefined ) { const endIndex = BufferUtil . scan ( this . _rawContent , [ 13 , 10 , 13 , 10 ] ) ; if ( endIndex !== - 1 ) { const headerBuffer = this . _rawContent . slice ( 0 , endIndex ) ; this . parseHeaders ( headerBuffer . toString ( ) ) ; if ( endIndex + 4 < this . _rawContent . length ) { const bodyPart = this . _rawContent . slice ( ( endIndex + 4 ) ) ; this . appendBody ( bodyPart ) ; } } } else { this . appendBody ( data ) ; } } returns { boolean } public complete ( ) { if ( ! this . _complete ) { if ( this . _headers !== undefined ) { const chunked = this . hasHeader ( "Transfer-Encoding" ) && this . header ( "Transfer-Encoding" ) . toLowerCase ( ) === "chunked" ; if ( chunked && this . _rawBody !== undefined ) { const chunks = this . parseChunks ( ) ; if ( chunks !== null && chunks . length > 0 && chunks [ chunks . length - 1 ] . lastChunk ( ) ) { this . _chunks = chunks ; this . _complete = true ; } } else if ( this . _rawBody !== undefined ) { let length = this . _rawBody . length ; if ( this . hasHeader ( "Content-Length" ) ) { length = parseInt ( this . header ( "Content-Length" ) ) ; } this . _complete = this . _rawBody . length === length ; } } } return this . _complete ; } public header ( headerKey ) { let value = null ; if ( this . _headers !== undefined && this . hasHeader ( headerKey ) ) { value = this . _headers [ headerKey ] ; } return value ; } public hasHeader ( headerKey ) { return headerKey in this . _headers ; } public method ( ) { return this . _method ; } public uri ( ) { return this . _uri ; } public statusCode ( ) { return this . _statusCode ; } public chunked ( ) { let chunked = false ; if ( this . complete ( ) ) { if ( this . _chunks !== undefined ) { chunked = true ; } } return chunked ; } public raw ( ) { return this . _rawContent ; } public isJSON ( ) { return this . hasHeader ( "Content-Type" ) && this . header ( "Content-Type" ) === "application/json" ; } public body ( ) { let body ; if ( this . complete ( ) ) { body = BufferUtil . fromString ( "" ) ; if ( this . chunked ( ) ) { for ( let chunk of this . _chunks ) { body = Buffer . concat ( [ body , chunk . body ] ) ; } } else { body = this . _rawBody ; } } return body ; } public bodyAsJSON ( ) { let json ; if ( this . body ( ) !== undefined ) { json = JSON . parse ( this . body ( ) . toString ( ) ) ; } return json ; } returns { Array < HTTPChunk > } private parseChunks ( ) { let chunks = [ ] ; let body = this . _rawBody ; while ( true ) { const chunk = HTTPChunk . parse ( body ) ; if ( chunk !== null ) { chunks . push ( chunk ) ; } else { break ; } if ( chunk . lastChunk ( ) ) { break ; } body = body . slice ( chunk . lengthWithHeaderAndTrailer ( ) ) ; } return chunks ; } private appendBody ( bodyPart ) { if ( this . _rawBody === undefined ) { this . _rawBody = BufferUtil . fromString ( "" ) ; } this . _rawBody = Buffer . concat ( [ this . _rawBody , bodyPart ] ) ; } private parseHeaders ( headersString ) { this . _headers = { } ; const lines = headersString . split ( "\n" ) ; if ( lines [ 0 ] . startsWith ( "HTTP" ) ) { this . _statusLine = lines [ 0 ] ; const statusLineParts = this . _statusLine . split ( " " ) ; this . _statusCode = parseInt ( statusLineParts [ 1 ] ) ; } else { this . _requestLine = lines [ 0 ] ; const requestLineParts = this . _requestLine . split ( " " ) ; this . _method = requestLineParts [ 0 ] ; this . _uri = requestLineParts [ 1 ] ; } for ( let i = 1 ; i < lines . length ; i ++ ) { const headerLine = lines [ i ] ; const headerParts = headerLine . split ( ":" ) ; const key = headerParts [ 0 ] ; this . _headers [ key ] = headerParts [ 1 ] . trim ( ) ; } } } export class HTTPChunk { public constructor ( public body , public lengthString ) { } public length ( ) { return parseInt ( this . lengthString , 16 ) ; } public headerLength ( ) { return ( this . lengthString ) . length + 2 ; } returns { number } public lengthWithHeaderAndTrailer ( ) { return this . length ( ) + this . headerLength ( ) + 2 ; } public lastChunk ( ) { return ( this . length ( ) === 0 ) ; } param httpBody returns { HTTPChunk } public static parse ( httpBody ) { let chunkLengthString = HTTPChunk . parseLength ( httpBody ) ; if ( chunkLengthString === null ) { return null ; } let chunkLength = parseInt ( chunkLengthString , 16 ) ; let chunkStartIndex = chunkLengthString . length + 2 ; let endIndex = chunkStartIndex + chunkLength ; if ( httpBody . length < endIndex ) { return null ; } let chunkBody = httpBody . slice ( chunkStartIndex , chunkStartIndex + chunkLength ) ; return new HTTPChunk ( chunkBody , chunkLengthString ) ; } param httpBody returns { number } public static parseLength ( httpBody ) { let index = BufferUtil . scan ( httpBody , [ 13 , 10 ] ) ; if ( index === - 1 ) { return null ; } let chunkLengthString = "" ; for ( let i = 0 ; i < index ; i ++ ) { let char = String . fromCharCode ( httpBody [ i ] ) ; if ( isNaN ( parseInt ( char , 16 ) ) ) { throw new RangeError ( "Invalid character found in chunk length - something went wrong! " + char ) ; } chunkLengthString += String . fromCharCode ( httpBody [ i ] ) ; } return chunkLengthString ; } }