<commit_before>import { createHash, Hash } from "crypto"
import _debug from "debug"
import { createWriteStream } from "fs-extra-p"
import { IncomingMessage, OutgoingHttpHeaders, RequestOptions } from "http"
import { Socket } from "net"
import { Transform } from "stream"
import { URL } from "url"
import { CancellationToken } from "./CancellationToken"
import { newError } from "./index"
import { ProgressCallbackTransform, ProgressInfo } from "./ProgressCallbackTransform"

const debug = _debug("electron-builder")

export interface RequestHeaders extends OutgoingHttpHeaders {
  [key: string]: string
}

export interface DownloadOptions {
  readonly headers?: OutgoingHttpHeaders | null
  readonly sha2?: string | null
  readonly sha512?: string | null

  readonly cancellationToken: CancellationToken

  // noinspection JSUnusedLocalSymbols
  onProgress?(progress: ProgressInfo): void
}

export function createHttpError(response: IncomingMessage, description: any | null = null) {
  return new HttpError(response.statusCode || -1, `${response.statusCode} ${response.statusMessage}` + (description == null ? "" : ("\n" + JSON.stringify(description, null, "  "))) + "\nHeaders: " + safeStringifyJson(response.headers), description)
}

const HTTP_STATUS_CODES = new Map<number, string>([
  [429, "Too many requests"],
  [400, "Bad request"],
  [403, "Forbidden"],
  [404, "Not found"],
  [405, "Method not allowed"],
  [406, "Not acceptable"],
  [408, "Request timeout"],
  [413, "Request entity too large"],
  [500, "Internal server error"],
  [502, "Bad gateway"],
  [503, "Service unavailable"],
  [504, "Gateway timeout"],
  [505, "HTTP version not supported"],
])

export class HttpError extends Error {
  constructor(readonly statusCode: number, message: string = `HTTP error: ${HTTP_STATUS_CODES.get(statusCode) || statusCode}`, readonly description: any | null = null) {
    super(message)

    this.name = "HttpError"
  }
}

export function parseJson(result: Promise<string | null>) {
  return result.then(it => it == null || it.length === 0 ? null : JSON.parse(it))
}

export abstract class HttpExecutor<REQUEST> {
  protected readonly maxRedirects = 10

  request(options: RequestOptions, cancellationToken: CancellationToken = new CancellationToken(), data?: { [name: string]: any; } | null): Promise<string | null> {
    configureRequestOptions(options)
    const encodedData = data == null ? undefined : Buffer.from(JSON.stringify(data))
    if (encodedData != null) {
      options.method = "post"
      options.headers!["Content-Type"] = "application/json"
      options.headers!["Content-Length"] = encodedData.length
    }
    return this.doApiRequest(options, cancellationToken, it => {
      (it as any).end(encodedData)
    })
  }

  doApiRequest(options: RequestOptions, cancellationToken: CancellationToken, requestProcessor: (request: REQUEST, reject: (error: Error) => void) => void, redirectCount: number = 0): Promise<string> {
    if (debug.enabled) {
      debug(`Request: ${safeStringifyJson(options)}`)
    }

    return cancellationToken.createPromise<string>((resolve, reject, onCancel) => {
      const request = this.createRequest(options, (response: any) => {
        try {
          this.handleResponse(response, options, cancellationToken, resolve, reject, redirectCount, requestProcessor)
        }
        catch (e) {
          reject(e)
        }
      })
      this.addErrorAndTimeoutHandlers(request, reject)
      this.addRedirectHandlers(request, options, reject, redirectCount, options => {
        this.doApiRequest(options, cancellationToken, requestProcessor, redirectCount)
          .then(resolve)
          .catch(reject)
      })
      requestProcessor(request, reject)
      onCancel(() => request.abort())
    })
  }

  // noinspection JSUnusedLocalSymbols
  protected addRedirectHandlers(request: any, options: RequestOptions, reject: (error: Error) => void, redirectCount: number, handler: (options: RequestOptions) => void) {
    // not required for NodeJS
  }

  addErrorAndTimeoutHandlers(request: any, reject: (error: Error) => void) {
    this.addTimeOutHandler(request, reject)
    request.on("error", reject)
    request.on("aborted", () => {
      reject(new Error("Request has been aborted by the server"))
    })
  }

  private handleResponse(response: IncomingMessage,
                         options: RequestOptions,
                         cancellationToken: CancellationToken,
                         resolve: (data?: any) => void,
                         reject: (error: Error) => void,
                         redirectCount: number,
                         requestProcessor: (request: REQUEST, reject: (error: Error) => void) => void) {
    if (debug.enabled) {
      debug(`Response: ${response.statusCode} ${response.statusMessage}, request options: ${safeStringifyJson(options)}`)
    }

    // we handle any other >= 400 error on request end (read detailed message in the response body)
    if (response.statusCode === 404) {
      // error is clear, we don't need to read detailed error description
      reject(createHttpError(response, `method: ${options.method} url: ${options.protocol || "https:"}//${options.hostname}${options.path}

Please double check that your authentication token is correct. Due to security reasons actual status maybe not reported, but 404.<commit_msg>fix(builder-util-runtime): fix description when request 404 (#3964)<commit_after>import { createHash, Hash } from "crypto"
import _debug from "debug"
import { createWriteStream } from "fs-extra-p"
import { IncomingMessage, OutgoingHttpHeaders, RequestOptions } from "http"
import { Socket } from "net"
import { Transform } from "stream"
import { URL } from "url"
import { CancellationToken } from "./CancellationToken"
import { newError } from "./index"
import { ProgressCallbackTransform, ProgressInfo } from "./ProgressCallbackTransform"

const debug = _debug("electron-builder")

export interface RequestHeaders extends OutgoingHttpHeaders {
  [key: string]: string
}

export interface DownloadOptions {
  readonly headers?: OutgoingHttpHeaders | null
  readonly sha2?: string | null
  readonly sha512?: string | null

  readonly cancellationToken: CancellationToken

  // noinspection JSUnusedLocalSymbols
  onProgress?(progress: ProgressInfo): void
}

export function createHttpError(response: IncomingMessage, description: any | null = null) {
  return new HttpError(response.statusCode || -1, `${response.statusCode} ${response.statusMessage}` + (description == null ? "" : ("\n" + JSON.stringify(description, null, "  "))) + "\nHeaders: " + safeStringifyJson(response.headers), description)
}

const HTTP_STATUS_CODES = new Map<number, string>([
  [429, "Too many requests"],
  [400, "Bad request"],
  [403, "Forbidden"],
  [404, "Not found"],
  [405, "Method not allowed"],
  [406, "Not acceptable"],
  [408, "Request timeout"],
  [413, "Request entity too large"],
  [500, "Internal server error"],
  [502, "Bad gateway"],
  [503, "Service unavailable"],
  [504, "Gateway timeout"],
  [505, "HTTP version not supported"],
])

export class HttpError extends Error {
  constructor(readonly statusCode: number, message: string = `HTTP error: ${HTTP_STATUS_CODES.get(statusCode) || statusCode}`, readonly description: any | null = null) {
    super(message)

    this.name = "HttpError"
  }
}

export function parseJson(result: Promise<string | null>) {
  return result.then(it => it == null || it.length === 0 ? null : JSON.parse(it))
}

export abstract class HttpExecutor<REQUEST> {
  protected readonly maxRedirects = 10

  request(options: RequestOptions, cancellationToken: CancellationToken = new CancellationToken(), data?: { [name: string]: any; } | null): Promise<string | null> {
    configureRequestOptions(options)
    const encodedData = data == null ? undefined : Buffer.from(JSON.stringify(data))
    if (encodedData != null) {
      options.method = "post"
      options.headers!["Content-Type"] = "application/json"
      options.headers!["Content-Length"] = encodedData.length
    }
    return this.doApiRequest(options, cancellationToken, it => {
      (it as any).end(encodedData)
    })
  }

  doApiRequest(options: RequestOptions, cancellationToken: CancellationToken, requestProcessor: (request: REQUEST, reject: (error: Error) => void) => void, redirectCount: number = 0): Promise<string> {
    if (debug.enabled) {
      debug(`Request: ${safeStringifyJson(options)}`)
    }

    return cancellationToken.createPromise<string>((resolve, reject, onCancel) => {
      const request = this.createRequest(options, (response: any) => {
        try {
          this.handleResponse(response, options, cancellationToken, resolve, reject, redirectCount, requestProcessor)
        }
        catch (e) {
          reject(e)
        }
      })
      this.addErrorAndTimeoutHandlers(request, reject)
      this.addRedirectHandlers(request, options, reject, redirectCount, options => {
        this.doApiRequest(options, cancellationToken, requestProcessor, redirectCount)
          .then(resolve)
          .catch(reject)
      })
      requestProcessor(request, reject)
      onCancel(() => request.abort())
    })
  }

  // noinspection JSUnusedLocalSymbols
  protected addRedirectHandlers(request: any, options: RequestOptions, reject: (error: Error) => void, redirectCount: number, handler: (options: RequestOptions) => void) {
    // not required for NodeJS
  }

  addErrorAndTimeoutHandlers(request: any, reject: (error: Error) => void) {
    this.addTimeOutHandler(request, reject)
    request.on("error", reject)
    request.on("aborted", () => {
      reject(new Error("Request has been aborted by the server"))
    })
  }

  private handleResponse(response: IncomingMessage,
                         options: RequestOptions,
                         cancellationToken: CancellationToken,
                         resolve: (data?: any) => void,
                         reject: (error: Error) => void,
                         redirectCount: number,
                         requestProcessor: (request: REQUEST, reject: (error: Error) => void) => void) {
    if (debug.enabled) {
      debug(`Response: ${response.statusCode} ${response.statusMessage}, request options: ${safeStringifyJson(options)}`)
    }

    // we handle any other >= 400 error on request end (read detailed message in the response body)
    if (response.statusCode === 404) {
      // error is clear, we don't need to read detailed error description
      reject(createHttpError(response, `method: ${options.method || "GET"} url: ${options.protocol || "https:"}//${options.hostname}${options.port ? `:${options.port}` : ""}${options.path}

Please double check that your authentication token is correct. Due to security reasons actual status maybe not reported, but 404.
{'fraction_non_alphanumeric': 0.1269387001477105, 'fraction_numerical': 0.011170605612998524, 'mean_word_length': 3.82109479305741, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2973624', 'n_tokens_mistral': 3014, 'n_tokens_neox': 2923, 'n_words': 838}