"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.filterCookies = filterCookies;
exports.rewriteCookies = rewriteCookies;
exports.parsedURL = parsedURL;
exports.stripFragmentFromUrl = stripFragmentFromUrl;
exports.singleHeader = singleHeader;
exports.mergeHeaders = mergeHeaders;
exports.STATUS_TEXTS = exports.WebSocket = exports.InterceptedResponse = exports.Response = exports.Route = exports.Request = void 0;

var _utils = require("../utils/utils");

var _instrumentation = require("./instrumentation");

/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function filterCookies(cookies, urls) {
  const parsedURLs = urls.map(s => new URL(s)); // Chromiums's cookies are missing sameSite when it is 'None'

  return cookies.filter(c => {
    // Firefox and WebKit can return cookies with empty values.
    if (!c.value) return false;
    if (!parsedURLs.length) return true;

    for (const parsedURL of parsedURLs) {
      let domain = c.domain;
      if (!domain.startsWith('.')) domain = '.' + domain;
      if (!('.' + parsedURL.hostname).endsWith(domain)) continue;
      if (!parsedURL.pathname.startsWith(c.path)) continue;
      if (parsedURL.protocol !== 'https:' && c.secure) continue;
      return true;
    }

    return false;
  });
}

function rewriteCookies(cookies) {
  return cookies.map(c => {
    (0, _utils.assert)(c.name, 'Cookie should have a name');
    (0, _utils.assert)(c.value, 'Cookie should have a value');
    (0, _utils.assert)(c.url || c.domain && c.path, 'Cookie should have a url or a domain/path pair');
    (0, _utils.assert)(!(c.url && c.domain), 'Cookie should have either url or domain');
    (0, _utils.assert)(!(c.url && c.path), 'Cookie should have either url or domain');
    const copy = { ...c
    };

    if (copy.url) {
      (0, _utils.assert)(copy.url !== 'about:blank', `Blank page can not have cookie "${c.name}"`);
      (0, _utils.assert)(!copy.url.startsWith('data:'), `Data URL page can not have cookie "${c.name}"`);
      const url = new URL(copy.url);
      copy.domain = url.hostname;
      copy.path = url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);
      copy.secure = url.protocol === 'https:';
    }

    return copy;
  });
}

function parsedURL(url) {
  try {
    return new URL(url);
  } catch (e) {
    return null;
  }
}

function stripFragmentFromUrl(url) {
  if (!url.includes('#')) return url;
  return url.substring(0, url.indexOf('#'));
}

class Request extends _instrumentation.SdkObject {
  constructor(routeDelegate, frame, redirectedFrom, documentId, url, resourceType, method, postData, headers) {
    super(frame, 'request');
    this._routeDelegate = void 0;
    this._response = null;
    this._redirectedFrom = void 0;
    this._redirectedTo = null;
    this._documentId = void 0;
    this._isFavicon = void 0;
    this._failureText = null;
    this._url = void 0;
    this._resourceType = void 0;
    this._method = void 0;
    this._postData = void 0;
    this._headers = void 0;
    this._headersMap = new Map();
    this._frame = void 0;
    this._waitForResponsePromise = void 0;

    this._waitForResponsePromiseCallback = () => {};

    this._responseEndTiming = -1;
    (0, _utils.assert)(!url.startsWith('data:'), 'Data urls should not fire requests');
    (0, _utils.assert)(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects');
    this._routeDelegate = routeDelegate;
    this._frame = frame;
    this._redirectedFrom = redirectedFrom;
    if (redirectedFrom) redirectedFrom._redirectedTo = this;
    this._documentId = documentId;
    this._url = stripFragmentFromUrl(url);
    this._resourceType = resourceType;
    this._method = method;
    this._postData = postData;
    this._headers = headers;

    for (const {
      name,
      value
    } of this._headers) this._headersMap.set(name.toLowerCase(), value);

    this._waitForResponsePromise = new Promise(f => this._waitForResponsePromiseCallback = f);
    this._isFavicon = url.endsWith('/favicon.ico');
  }

  _setFailureText(failureText) {
    this._failureText = failureText;

    this._waitForResponsePromiseCallback(null);
  }

  url() {
    return this._url;
  }

  resourceType() {
    return this._resourceType;
  }

  method() {
    return this._method;
  }

  postDataBuffer() {
    return this._postData;
  }

  headers() {
    return this._headers;
  }

  headerValue(name) {
    return this._headersMap.get(name);
  }

  response() {
    return this._waitForResponsePromise;
  }

  _existingResponse() {
    return this._response;
  }

  _setResponse(response) {
    this._response = response;

    this._waitForResponsePromiseCallback(response);
  }

  _finalRequest() {
    return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
  }

  frame() {
    return this._frame;
  }

  isNavigationRequest() {
    return !!this._documentId;
  }

  redirectedFrom() {
    return this._redirectedFrom;
  }

  failure() {
    if (this._failureText === null) return null;
    return {
      errorText: this._failureText
    };
  }

  _route() {
    if (!this._routeDelegate) return null;
    return new Route(this, this._routeDelegate);
  }

  updateWithRawHeaders(headers) {
    this._headers = headers;

    this._headersMap.clear();

    for (const {
      name,
      value
    } of this._headers) this._headersMap.set(name.toLowerCase(), value);

    if (!this._headersMap.has('host')) {
      const host = new URL(this._url).host;

      this._headers.push({
        name: 'host',
        value: host
      });

      this._headersMap.set('host', host);
    }
  }

}

exports.Request = Request;

class Route extends _instrumentation.SdkObject {
  constructor(request, delegate) {
    super(request.frame(), 'route');
    this._request = void 0;
    this._delegate = void 0;
    this._handled = false;
    this._response = null;
    this._request = request;
    this._delegate = delegate;
  }

  request() {
    return this._request;
  }

  async abort(errorCode = 'failed') {
    (0, _utils.assert)(!this._handled, 'Route is already handled!');
    this._handled = true;
    await this._delegate.abort(errorCode);
  }

  async fulfill(overrides) {
    var _this$_response, _this$_response2;

    (0, _utils.assert)(!this._handled, 'Route is already handled!');
    this._handled = true;
    let body = overrides.body;
    let isBase64 = overrides.isBase64 || false;

    if (!body) {
      if (this._response) {
        body = (await this._delegate.responseBody(true)).toString('utf8');
        isBase64 = false;
      } else {
        body = '';
        isBase64 = false;
      }
    }

    await this._delegate.fulfill({
      status: overrides.status || ((_this$_response = this._response) === null || _this$_response === void 0 ? void 0 : _this$_response.status()) || 200,
      headers: overrides.headers || ((_this$_response2 = this._response) === null || _this$_response2 === void 0 ? void 0 : _this$_response2.headers()) || [],
      body,
      isBase64
    });
  }

  async continue(overrides = {}) {
    (0, _utils.assert)(!this._handled, 'Route is already handled!');
    (0, _utils.assert)(!this._response, 'Cannot call continue after response interception!');

    if (overrides.url) {
      const newUrl = new URL(overrides.url);
      const oldUrl = new URL(this._request.url());
      if (oldUrl.protocol !== newUrl.protocol) throw new Error('New URL must have same protocol as overridden URL');
    }

    this._response = await this._delegate.continue(overrides);
    return this._response;
  }

  async responseBody() {
    (0, _utils.assert)(!this._handled, 'Route is already handled!');
    return this._delegate.responseBody(false);
  }

}

exports.Route = Route;

class Response extends _instrumentation.SdkObject {
  constructor(request, status, statusText, headers, timing, getResponseBodyCallback) {
    super(request.frame(), 'response');
    this._request = void 0;
    this._contentPromise = null;
    this._finishedPromise = void 0;

    this._finishedPromiseCallback = () => {};

    this._status = void 0;
    this._statusText = void 0;
    this._url = void 0;
    this._headers = void 0;
    this._headersMap = new Map();
    this._getResponseBodyCallback = void 0;
    this._timing = void 0;
    this._serverAddrPromise = void 0;

    this._serverAddrPromiseCallback = () => {};

    this._securityDetailsPromise = void 0;

    this._securityDetailsPromiseCallback = () => {};

    this._request = request;
    this._timing = timing;
    this._status = status;
    this._statusText = statusText;
    this._url = request.url();
    this._headers = headers;

    for (const {
      name,
      value
    } of this._headers) this._headersMap.set(name.toLowerCase(), value);

    this._getResponseBodyCallback = getResponseBodyCallback;
    this._serverAddrPromise = new Promise(f => {
      this._serverAddrPromiseCallback = f;
    });
    this._securityDetailsPromise = new Promise(f => {
      this._securityDetailsPromiseCallback = f;
    });
    this._finishedPromise = new Promise(f => {
      this._finishedPromiseCallback = f;
    });

    this._request._setResponse(this);
  }

  _serverAddrFinished(addr) {
    this._serverAddrPromiseCallback(addr);
  }

  _securityDetailsFinished(securityDetails) {
    this._securityDetailsPromiseCallback(securityDetails);
  }

  _requestFinished(responseEndTiming, error) {
    this._request._responseEndTiming = Math.max(responseEndTiming, this._timing.responseStart);

    this._finishedPromiseCallback({
      error
    });
  }

  url() {
    return this._url;
  }

  status() {
    return this._status;
  }

  statusText() {
    return this._statusText;
  }

  headers() {
    return this._headers;
  }

  headerValue(name) {
    return this._headersMap.get(name);
  }

  finished() {
    return this._finishedPromise.then(({
      error
    }) => error ? new Error(error) : null);
  }

  timing() {
    return this._timing;
  }

  async serverAddr() {
    return (await this._serverAddrPromise) || null;
  }

  async securityDetails() {
    return (await this._securityDetailsPromise) || null;
  }

  body() {
    if (!this._contentPromise) {
      this._contentPromise = this._finishedPromise.then(async ({
        error
      }) => {
        if (error) throw new Error(error);
        return this._getResponseBodyCallback();
      });
    }

    return this._contentPromise;
  }

  request() {
    return this._request;
  }

  frame() {
    return this._request.frame();
  }

}

exports.Response = Response;

class InterceptedResponse extends _instrumentation.SdkObject {
  constructor(request, status, statusText, headers) {
    super(request.frame(), 'interceptedResponse');
    this._request = void 0;
    this._status = void 0;
    this._statusText = void 0;
    this._headers = void 0;
    this._request = request;
    this._status = status;
    this._statusText = statusText;
    this._headers = headers;
  }

  status() {
    return this._status;
  }

  statusText() {
    return this._statusText;
  }

  headers() {
    return this._headers;
  }

  request() {
    return this._request;
  }

}

exports.InterceptedResponse = InterceptedResponse;

class WebSocket extends _instrumentation.SdkObject {
  constructor(parent, url) {
    super(parent, 'ws');
    this._url = void 0;
    this._url = url;
  }

  url() {
    return this._url;
  }

  frameSent(opcode, data) {
    this.emit(WebSocket.Events.FrameSent, {
      opcode,
      data
    });
  }

  frameReceived(opcode, data) {
    this.emit(WebSocket.Events.FrameReceived, {
      opcode,
      data
    });
  }

  error(errorMessage) {
    this.emit(WebSocket.Events.SocketError, errorMessage);
  }

  closed() {
    this.emit(WebSocket.Events.Close);
  }

}

exports.WebSocket = WebSocket;
WebSocket.Events = {
  Close: 'close',
  SocketError: 'socketerror',
  FrameReceived: 'framereceived',
  FrameSent: 'framesent'
};
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
const STATUS_TEXTS = {
  '100': 'Continue',
  '101': 'Switching Protocols',
  '102': 'Processing',
  '103': 'Early Hints',
  '200': 'OK',
  '201': 'Created',
  '202': 'Accepted',
  '203': 'Non-Authoritative Information',
  '204': 'No Content',
  '205': 'Reset Content',
  '206': 'Partial Content',
  '207': 'Multi-Status',
  '208': 'Already Reported',
  '226': 'IM Used',
  '300': 'Multiple Choices',
  '301': 'Moved Permanently',
  '302': 'Found',
  '303': 'See Other',
  '304': 'Not Modified',
  '305': 'Use Proxy',
  '306': 'Switch Proxy',
  '307': 'Temporary Redirect',
  '308': 'Permanent Redirect',
  '400': 'Bad Request',
  '401': 'Unauthorized',
  '402': 'Payment Required',
  '403': 'Forbidden',
  '404': 'Not Found',
  '405': 'Method Not Allowed',
  '406': 'Not Acceptable',
  '407': 'Proxy Authentication Required',
  '408': 'Request Timeout',
  '409': 'Conflict',
  '410': 'Gone',
  '411': 'Length Required',
  '412': 'Precondition Failed',
  '413': 'Payload Too Large',
  '414': 'URI Too Long',
  '415': 'Unsupported Media Type',
  '416': 'Range Not Satisfiable',
  '417': 'Expectation Failed',
  '418': 'I\'m a teapot',
  '421': 'Misdirected Request',
  '422': 'Unprocessable Entity',
  '423': 'Locked',
  '424': 'Failed Dependency',
  '425': 'Too Early',
  '426': 'Upgrade Required',
  '428': 'Precondition Required',
  '429': 'Too Many Requests',
  '431': 'Request Header Fields Too Large',
  '451': 'Unavailable For Legal Reasons',
  '500': 'Internal Server Error',
  '501': 'Not Implemented',
  '502': 'Bad Gateway',
  '503': 'Service Unavailable',
  '504': 'Gateway Timeout',
  '505': 'HTTP Version Not Supported',
  '506': 'Variant Also Negotiates',
  '507': 'Insufficient Storage',
  '508': 'Loop Detected',
  '510': 'Not Extended',
  '511': 'Network Authentication Required'
};
exports.STATUS_TEXTS = STATUS_TEXTS;

function singleHeader(name, value) {
  return [{
    name,
    value
  }];
}

function mergeHeaders(headers) {
  const lowerCaseToValue = new Map();
  const lowerCaseToOriginalCase = new Map();

  for (const h of headers) {
    if (!h) continue;

    for (const {
      name,
      value
    } of h) {
      const lower = name.toLowerCase();
      lowerCaseToOriginalCase.set(lower, name);
      lowerCaseToValue.set(lower, value);
    }
  }

  const result = [];

  for (const [lower, value] of lowerCaseToValue) result.push({
    name: lowerCaseToOriginalCase.get(lower),
    value
  });

  return result;
}
//# sourceMappingURL=network.js.map