/* This file is part of GNU Taler (C) 2019 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler 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 General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see SPDX-License-Identifier: AGPL3.0-or-later */ /** * Imports. */ import { Logger } from "@gnu-taler/taler-util"; import { TalerError } from "./errors.js"; import { encodeBody, getDefaultHeaders, HttpLibArgs } from "./http-common.js"; import { Headers, HttpRequestLibrary, HttpRequestOptions, HttpResponse, } from "./http.js"; import { RequestThrottler, TalerErrorCode, URL } from "./index.js"; import { qjsOs } from "./qtart.js"; const logger = new Logger("http-impl.qtart.ts"); const textDecoder = new TextDecoder(); /** * Implementation of the HTTP request library interface for node. */ export class HttpLibImpl implements HttpRequestLibrary { private throttle = new RequestThrottler(); private throttlingEnabled = true; private requireTls = false; constructor(args?: HttpLibArgs) { this.throttlingEnabled = args?.enableThrottling ?? true; this.requireTls = args?.requireTls ?? false; } /** * Set whether requests should be throttled. */ setThrottling(enabled: boolean): void { this.throttlingEnabled = enabled; } async fetch(url: string, opt?: HttpRequestOptions): Promise { const method = (opt?.method ?? "GET").toUpperCase(); logger.trace(`Requesting ${method} ${url}`); const parsedUrl = new URL(url); if (this.throttlingEnabled && this.throttle.applyThrottle(url)) { throw TalerError.fromDetail( TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, { requestMethod: method, requestUrl: url, throttleStats: this.throttle.getThrottleStats(url), }, `request to origin ${parsedUrl.origin} was throttled`, ); } if (this.requireTls && parsedUrl.protocol !== "https:") { throw TalerError.fromDetail( TalerErrorCode.WALLET_NETWORK_ERROR, { requestMethod: method, requestUrl: url, }, `request to ${parsedUrl.origin} is not possible with protocol ${parsedUrl.protocol}`, ); } let data: ArrayBuffer | undefined = undefined; const requestHeadersMap = { ...getDefaultHeaders(method), ...opt?.headers }; let headersList: string[] = []; for (let headerName of Object.keys(requestHeadersMap)) { headersList.push(`${headerName}: ${requestHeadersMap[headerName]}`); } if (method === "POST") { data = encodeBody(opt?.body); } const res = await qjsOs.fetchHttp(url, { method, data, headers: headersList, }); const headers: Headers = new Headers(); if (res.headers) { for (const headerStr of res.headers) { const splitPos = headerStr.indexOf(":"); if (splitPos < 0) { continue; } const headerName = headerStr.slice(0, splitPos).trim().toLowerCase(); const headerValue = headerStr.slice(splitPos + 1).trim(); headers.set(headerName, headerValue); } } return { requestMethod: method, headers, async bytes() { return res.data; }, json() { const text = textDecoder.decode(res.data); return JSON.parse(text); }, async text() { const text = textDecoder.decode(res.data); return text; }, requestUrl: url, status: res.status, }; } }