repo_description: Promise based HTTP client for the browser and node.js
repo_topics: hacktoberfest http-client javascript nodejs promise
code: 'use strict';
  
  import utils from '../utils.js';
  import parseHeaders from '../helpers/parseHeaders.js';
  
  const $internals = Symbol('internals');
  
  const INVALID_HEADER_VALUE_CHARS_RE = /[^\x09\x20-\x7E\x80-\xFF]/g;
  
  function trimSPorHTAB(str) {
    let start = 0;
    let end = str.length;
  
    while (start < end) {
      const code = str.charCodeAt(start);
  
      if (code !== 0x09 && code !== 0x20) {
        break;
      }
  
      start += 1;
    }
  
    while (end > start) {
      const code = str.charCodeAt(end - 1);
  
      if (code !== 0x09 && code !== 0x20) {
        break;
      }
  
      end -= 1;
    }
  
    return start === 0 && end === str.length ? str : str.slice(start, end);
  }
  
  function normalizeHeader(header) {
    return header && String(header).trim().toLowerCase();
  }
  
  function sanitizeHeaderValue(str) {
    return trimSPorHTAB(str.replace(INVALID_HEADER_VALUE_CHARS_RE, ''));
  }
  
  function normalizeValue(value) {
    if (value === false || value == null) {
      return value;
    }
  
    return utils.isArray(value) ? value.map(normalizeValue) : sanitizeHeaderValue(String(value));
  }
  
  function parseTokens(str) {
    const tokens = Object.create(null);
    const tokensRE = /([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;
    let match;
  
    while ((match = tokensRE.exec(str))) {
      tokens[match[1]] = match[2];
    }
  
    return tokens;
  }
  
  const isValidHeaderName = (str) => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str.trim());
  
  function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) {
    if (utils.isFunction(filter)) {
      return filter.call(this, value, header);
    }
  
    if (isHeaderNameFilter) {
      value = header;
    }
  
    if (!utils.isString(value)) return;
  
    if (utils.isString(filter)) {
      return value.indexOf(filter) !== -1;
    }
  
    if (utils.isRegExp(filter)) {
      return filter.test(value);
    }
  }
  
  function formatHeader(header) {
    return header
      .trim()
      .toLowerCase()
      .replace(/([a-z\d])(\w*)/g, (w, char, str) => {
        return char.toUpperCase() + str;
      });
  }
  
  function buildAccessors(obj, header) {
    const accessorName = utils.toCamelCase(' ' + header);
  
    ['get', 'set', 'has'].forEach((methodName) => {
      Object.defineProperty(obj, methodName + accessorName, {
        value: function (arg1, arg2, arg3) {
          return this[methodName].call(this, header, arg1, arg2, arg3);
        },
        configurable: true,
      });
    });
  }
  
  class AxiosHeaders {
    constructor(headers) {
      headers && this.set(headers);
    }
  
    set(header, valueOrRewrite, rewrite) {
      const self = this;
  
      function setHeader(_value, _header, _rewrite) {
        const lHeader = normalizeHeader(_header);
  
        if (!lHeader) {
          throw new Error('header name must be a non-empty string');
        }
  
        const key = utils.findKey(self, lHeader);
  
        if (
          !key ||
          self[key] === undefined ||
          _rewrite === true ||
          (_rewrite === undefined && self[key] !== false)
        ) {
          self[key || _header] = normalizeValue(_value);
        }
      }
  
      const setHeaders = (headers, _rewrite) =>
        utils.forEach(headers, (_value, _header) => setHeader(_value, _header, _rewrite));
  
      if (utils.isPlainObject(header) || header instanceof this.constructor) {
        setHeaders(header, valueOrRewrite);
      } else if (utils.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) {
        setHeaders(parseHeaders(header), valueOrRewrite);
      } else if (utils.isObject(header) && utils.isIterable(header)) {
        let obj = {},
          dest,
          key;
        for (const entry of header) {
          if (!utils.isArray(entry)) {
            throw TypeError('Object iterator must return a key-value pair');
          }
  
          obj[(key = entry[0])] = (dest = obj[key])
            ? utils.isArray(dest)
              ? [...dest, entry[1]]
              : [dest, entry[1]]
            : entry[1];
        }
  
        setHeaders(obj, valueOrRewrite);
      } else {
        header != null && setHeader(valueOrRewrite, header, rewrite);
      }
  
      return this;
    }
  
    get(header, parser) {
      header = normalizeHeader(header);
  
      if (header) {
        const key = utils.findKey(this, header);
  
        if (key) {
          const value = this[key];
  
          if (!parser) {
            return value;
          }
  
          if (parser === true) {
            return parseTokens(value);
          }
  
          if (utils.isFunction(parser)) {
            return parser.call(this, value, key);
          }
  
          if (utils.isRegExp(parser)) {
            return parser.exec(value);
          }
  
          throw new TypeError('parser must be boolean|regexp|function');
        }
      }
    }
  
    has(header, matcher) {
      header = normalizeHeader(header);
  
      if (header) {
        const key = utils.findKey(this, header);
  
        return !!(
          key &&
          this[key] !== undefined &&
          (!matcher || matchHeaderValue(this, this[key], key, matcher))
        );
      }
  
      return false;
    }
  
    delete(header, matcher) {
      const self = this;
      let deleted = false;
  
      function deleteHeader(_header) {
        _header = normalizeHeader(_header);
  
        if (_header) {
          const key = utils.findKey(self, _header);
  
          if (key && (!matcher || matchHeaderValue(self, self[key], key, matcher))) {
            delete self[key];
  
            deleted = true;
          }
        }
      }
  
      if (utils.isArray(header)) {
        header.forEach(deleteHeader);
      } else {
        deleteHeader(header);
      }
  
      return deleted;
    }
  
    clear(matcher) {
      const keys = Object.keys(this);
      let i = keys.length;
      let deleted = false;
  
      while (i--) {
        const key = keys[i];
        if (!matcher || matchHeaderValue(this, this[key], key, matcher, true)) {
          delete this[key];
          deleted = true;
        }
      }
  
      return deleted;
    }
  
    normalize(format) {
      const self = this;
      const headers = {};
  
      utils.forEach(this, (value, header) => {
        const key = utils.findKey(headers, header);
  
        if (key) {
          self[key] = normalizeValue(value);
          delete self[header];
          return;
        }
  
        const normalized = format ? formatHeader(header) : String(header).trim();
  
        if (normalized !== header) {
          delete self[header];
        }
  
        self[normalized] = normalizeValue(value);
  
        headers[normalized] = true;
      });
  
      return this;
    }
  
    concat(...targets) {
      return this.constructor.concat(this, ...targets);
    }
  
    toJSON(asStrings) {
      const obj = Object.create(null);
  
      utils.forEach(this, (value, header) => {
        value != null &&
          value !== false &&
          (obj[header] = asStrings && utils.isArray(value) ? value.join(', ') : value);
      });
  
      return obj;
    }
  
    [Symbol.iterator]() {
      return Object.entries(this.toJSON())[Symbol.iterator]();
    }
  
    toString() {
      return Object.entries(this.toJSON())
        .map(([header, value]) => header + ': ' + value)
        .join('\n');
    }
  
    getSetCookie() {
      return this.get('set-cookie') || [];
    }
  
    get [Symbol.toStringTag]() {
      return 'AxiosHeaders';
    }
  
    static from(thing) {
      return thing instanceof this ? thing : new this(thing);
    }
  
    static concat(first, ...targets) {
      const computed = new this(first);
  
      targets.forEach((target) => computed.set(target));
  
      return computed;
    }
  
    static accessor(header) {
      const internals =
        (this[$internals] =
        this[$internals] =
          {
            accessors: {},
          });
  
      const accessors = internals.accessors;
      const prototype = this.prototype;
  
      function defineAccessor(_header) {
        const lHeader = normalizeHeader(_header);
  
        if (!accessors[lHeader]) {
          buildAccessors(prototype, _header);
          accessors[lHeader] = true;
        }
      }
  
      utils.isArray(header) ? header.forEach(defineAccessor) : defineAccessor(header);
  
      return this;
    }
  }
  
  AxiosHeaders.accessor([
    'Content-Type',
    'Content-Length',
    'Accept',
    'Accept-Encoding',
    'User-Agent',
    'Authorization',
  ]);
  
  // reserved names hotfix
  utils.reduceDescriptors(AxiosHeaders.prototype, ({ value }, key) => {
    let mapped = key[0].toUpperCase() + key.slice(1); // map `set` => `Set`
    return {
      get: () => value,
      set(headerValue) {
        this[mapped] = headerValue;
      },
    };
  });
  
  utils.freezeMethods(AxiosHeaders);
  
  export default AxiosHeaders;
  