pluto-http-client
Version:
HTTP client for NodeJS. Inspired in the Java JAX-RS spec so you can expect excellence, versatility and extensibility.
362 lines (361 loc) • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpHeaderReader = exports.Event = void 0;
const cookie_1 = require("../core/cookie");
var Event;
(function (Event) {
Event[Event["Token"] = 0] = "Token";
Event[Event["QuotedString"] = 1] = "QuotedString";
Event[Event["Comment"] = 2] = "Comment";
Event[Event["Separator"] = 3] = "Separator";
Event[Event["Control"] = 4] = "Control";
})(Event = exports.Event || (exports.Event = {}));
class HttpHeaderReader {
constructor(header, processComments = false) {
this.header = header;
this.processComments = processComments;
this.index = 0;
this.length = header.length;
}
hasNext() {
return this.skipWhiteSpace();
}
get value() {
return this._value;
}
skipWhiteSpace() {
for (; this.index < this.length; this.index++) {
if (!this.isWhiteSpace(this.header.charAt(this.index))) {
return true;
}
}
return false;
}
isWhiteSpace(c) {
return HttpHeaderReader.WHITE_SPACE.has(c);
}
isSeparator(c) {
return HttpHeaderReader.SEPARATORS.has(c);
}
nextToken() {
const e = this.next(false);
if (e != Event.Token) {
throw new Error("Next event is not a Token:" + this.index);
}
return this._value || "";
}
nextSeparator(seperator) {
var _a, _b;
const e = this.next(false);
if (e != Event.Separator) {
throw new Error("Next event is not a Separator" + this.index);
}
if (seperator != ((_a = this._value) === null || _a === void 0 ? void 0 : _a.charAt(0))) {
throw new Error(`Expected separator '${seperator}' instead of ${(_b = this._value) === null || _b === void 0 ? void 0 : _b.charAt(0)} at index ${this.index}`);
}
}
hasNextSeparator(separator, skipWhiteSpace) {
if (skipWhiteSpace) {
this.skipWhiteSpace();
}
if (this.index >= this.length) {
return false;
}
let c = this.header.charAt(this.index);
return this.isSeparator(c) && c == separator;
}
next(skipWhiteSpace = true, preserveBackslash = false) {
this.event = this.process(this.getNextCharacter(skipWhiteSpace), preserveBackslash);
return this.event;
}
getNextCharacter(skipWhiteSpace) {
if (skipWhiteSpace) {
this.skipWhiteSpace();
}
if (this.index >= this.length) {
throw new Error("HTTP_HEADER_END_OF_HEADER:" + this.index);
}
return this.header.charCodeAt(this.index);
}
getType(c) {
return HttpHeaderReader.TYPE_TABLE[c];
}
isToken(c) {
return HttpHeaderReader.IS_TOKEN[c];
}
processQuotedString(preserveBackslash) {
let filter = false;
for (let start = ++this.index; this.index < this.length; this.index++) {
const c = this.header.charCodeAt(this.index);
if (!preserveBackslash && c == '\\'.charCodeAt(0)) {
this.index++;
filter = true;
}
else if (c == '\r'.charCodeAt(0)) {
filter = true;
}
else if (c == '"'.charCodeAt(0)) {
this._value = (filter) ? this.filterToken(this.header, start, this.index, preserveBackslash) : this.header.substring(start, this.index);
this.index++;
return;
}
}
throw Error("HTTP_HEADER_UNBALANCED_QUOTED" + this.index);
}
nextQuotedString() {
const e = this.next(false);
if (e != Event.QuotedString) {
throw new Error(`Next event is not a Quoted String. Index: ${this.index}`);
}
else {
return this._value;
}
}
nextTokenOrQuotedString(preserveBackslash) {
const e = this.next(false, preserveBackslash);
if (e != Event.Token && e != Event.QuotedString) {
throw new Error("Next event is not a Token or a Quoted String, " + this._value + this.index);
}
else {
return this._value || "";
}
}
filterToken(s, start, end, preserveBackslash = false) {
const buffer = [];
let c;
let gotEscape = false;
let gotCR = false;
for (let i = start; i < end; i++) {
c = s.charAt(i);
if (c == '\n' && gotCR) {
gotCR = false;
continue;
}
gotCR = false;
if (!gotEscape) {
if (!preserveBackslash && c == '\\') {
gotEscape = true;
}
else if (c == '\r') {
gotCR = true;
}
else {
buffer.push(c);
}
}
else {
buffer.push(c);
gotEscape = false;
}
}
return buffer.join("");
}
processComment() {
let filter = false;
let nesting;
let start;
for (start = ++this.index, nesting = 1; nesting > 0 && this.index < this.length; this.index++) {
let c = this.header.charAt(this.index);
if (c == '\\') {
this.index++;
filter = true;
}
else if (c == '\r') {
filter = true;
}
else if (c == '(') {
nesting++;
}
else if (c == ')') {
nesting--;
}
}
if (nesting != 0) {
throw new Error("HTTP_HEADER_UNBALANCED_COMMENTS" + this.index);
}
this._value = (filter) ? this.filterToken(this.header, start, this.index - 1) : this.header.substring(start, this.index - 1);
}
process(c, preserveBackslash) {
if (c > 127) {
this.index++;
return Event.Control;
}
switch (this.getType(c)) {
case HttpHeaderReader.TOKEN: {
const start = this.index;
for (this.index++; this.index < this.length; this.index++) {
if (!this.isToken(this.header.charCodeAt(this.index))) {
break;
}
}
this._value = this.header.substring(start, this.index);
return Event.Token;
}
case HttpHeaderReader.QUOTED_STRING:
this.processQuotedString(preserveBackslash);
return Event.QuotedString;
case HttpHeaderReader.COMMENT:
if (!this.processComments) {
throw new Error("HTTP_HEADER_COMMENTS_NOT_ALLOWED" + this.index);
}
this.processComment();
return Event.Comment;
case HttpHeaderReader.SEPARATOR:
this.index++;
this._value = String.fromCharCode(c);
return Event.Separator;
case HttpHeaderReader.CONTROL:
this.index++;
this._value = String.fromCharCode(c);
return Event.Control;
default:
throw new Error("HTTP_HEADER_WHITESPACE_NOT_ALLOWED:" + this.index);
}
}
static createTableType() {
const table = {};
const controlCharBound = 32;
for (let i = 0; i < controlCharBound; i++) {
table[i] = HttpHeaderReader.CONTROL;
}
table[127] = HttpHeaderReader.CONTROL;
for (let i = controlCharBound; i < 127; i++) {
table[i] = HttpHeaderReader.TOKEN;
}
for (const c of HttpHeaderReader.SEPARATORS.values()) {
table[c.charCodeAt(0)] = HttpHeaderReader.SEPARATOR;
}
table['('.charCodeAt(0)] = HttpHeaderReader.COMMENT;
table['"'.charCodeAt(0)] = HttpHeaderReader.QUOTED_STRING;
for (const c of HttpHeaderReader.WHITE_SPACE.values()) {
table[c.charCodeAt(0)] = -1;
}
return table;
}
static createTokenTable() {
const table = {};
for (let i = 0; i <= 127; i++) {
table[i] = (HttpHeaderReader.TYPE_TABLE[i] == HttpHeaderReader.TOKEN);
}
return table;
}
static readParameters(reader, fileNameFix = false) {
var _a;
let m = new Map();
while (reader.hasNext()) {
reader.nextSeparator(';');
while (reader.hasNextSeparator(';', true)) {
reader.next();
}
if (!reader.hasNext()) {
break;
}
let name = (_a = reader.nextToken()) === null || _a === void 0 ? void 0 : _a.toString().toLowerCase();
reader.nextSeparator('=');
let value;
if ("filename" === name && fileNameFix) {
value = reader.nextTokenOrQuotedString(true).toString();
value = value.substring(value.lastIndexOf('\\') + 1);
}
else {
value = reader.nextTokenOrQuotedString(false).toString();
}
m.set(name, value);
}
return m;
}
static appendQuotedIfWhitespace(buffer, s) {
if (/ /.test(s || "")) {
buffer.push(`"${s}"`);
}
else {
buffer.push(s);
}
}
static appendQuotedIfNonToken(b, value) {
if (value) {
const quote = !HttpHeaderReader.isTokenString(value);
if (quote) {
b.push('"');
}
HttpHeaderReader.appendEscapingQuotes(b, value);
if (quote) {
b.push('"');
}
}
}
static isTokenString(s) {
for (let idx = 0; idx < s.length; ++idx) {
const c = s.charCodeAt(idx);
if (!HttpHeaderReader.IS_TOKEN[c]) {
return false;
}
}
return true;
}
static appendEscapingQuotes(b, value) {
for (let i = 0; i < value.length; ++i) {
const c = value.charAt(i);
if (c == '"') {
b.push('\\');
}
b.push(c);
}
}
static parseCookie(header) {
if (!header) {
return;
}
let cookie = undefined;
const bites = header.split(/;/);
for (let bite of bites) {
const crumbs = bite.split("=", 2);
const name = crumbs.length > 0 ? crumbs[0].trim() : "";
let value = crumbs.length > 1 ? crumbs[1].trim() : "";
if (value.startsWith("\"") && value.endsWith("\"") && value.length > 1) {
value = value.substring(1, value.length - 1);
}
if (!cookie) {
cookie = new cookie_1.Cookie(name, value);
}
else {
let param = name.toLowerCase();
if (param.startsWith("comment")) {
cookie.comment = value;
}
else if (param.startsWith("domain")) {
cookie.domain = value;
}
else if (param.startsWith("max-age")) {
cookie.maxAge = Number.parseInt(value);
}
else if (param.startsWith("path")) {
cookie.path = value;
}
else if (param.startsWith("secure")) {
cookie.secure = true;
}
else if (param.startsWith("version")) {
cookie.version = Number.parseInt(value);
}
else if (param.startsWith("httponly")) {
cookie.httpOnly = true;
}
else if (param.startsWith("expires")) {
cookie.expiry = new Date(value);
}
}
}
return cookie;
}
}
exports.HttpHeaderReader = HttpHeaderReader;
HttpHeaderReader.TOKEN = 0;
HttpHeaderReader.QUOTED_STRING = 1;
HttpHeaderReader.COMMENT = 2;
HttpHeaderReader.SEPARATOR = 3;
HttpHeaderReader.CONTROL = 4;
HttpHeaderReader.WHITE_SPACE = new Set(['\t', '\r', '\n', ' ']);
HttpHeaderReader.SEPARATORS = new Set(['(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t']);
HttpHeaderReader.TYPE_TABLE = HttpHeaderReader.createTableType();
HttpHeaderReader.IS_TOKEN = HttpHeaderReader.createTokenTable();