'use strict'

const { describe, test } = require('node:test')
const util = require('../../lib/web/fetch/util')
const { HeadersList } = require('../../lib/web/fetch/headers')

test('responseURL', (t) => {
  t.plan(2)

  t.assert.ok(util.responseURL({
    urlList: [
      new URL('http://asd'),
      new URL('http://fgh')
    ]
  }))
  t.assert.ok(!util.responseURL({
    urlList: []
  }))
})

test('responseLocationURL', (t) => {
  t.plan(3)

  const acceptHeaderList = new HeadersList()
  acceptHeaderList.append('Accept', '*/*')

  const locationHeaderList = new HeadersList()
  locationHeaderList.append('Location', 'http://asd')

  t.assert.ok(!util.responseLocationURL({
    status: 200
  }))
  t.assert.ok(!util.responseLocationURL({
    status: 301,
    headersList: acceptHeaderList
  }))
  t.assert.ok(util.responseLocationURL({
    status: 301,
    headersList: locationHeaderList,
    urlList: [
      new URL('http://asd'),
      new URL('http://fgh')
    ]
  }))
})

test('requestBadPort', (t) => {
  t.plan(3)

  t.assert.strictEqual('allowed', util.requestBadPort({
    urlList: [new URL('https://asd')]
  }))
  t.assert.strictEqual('blocked', util.requestBadPort({
    urlList: [new URL('http://asd:7')]
  }))
  t.assert.strictEqual('blocked', util.requestBadPort({
    urlList: [new URL('https://asd:7')]
  }))
})

// https://html.spec.whatwg.org/multipage/origin.html#same-origin
// look at examples
test('sameOrigin', async (t) => {
  await t.test('first test', (t) => {
    const A = {
      protocol: 'https:',
      hostname: 'example.org',
      port: ''
    }

    const B = {
      protocol: 'https:',
      hostname: 'example.org',
      port: ''
    }

    t.assert.ok(util.sameOrigin(A, B))
  })

  await t.test('second test', (t) => {
    const A = {
      protocol: 'https:',
      hostname: 'example.org',
      port: '314'
    }

    const B = {
      protocol: 'https:',
      hostname: 'example.org',
      port: '420'
    }

    t.assert.ok(!util.sameOrigin(A, B))
  })

  await t.test('obviously shouldn\'t be equal', (t) => {
    t.assert.ok(!util.sameOrigin(
      { protocol: 'http:', hostname: 'example.org' },
      { protocol: 'https:', hostname: 'example.org' }
    ))

    t.assert.ok(!util.sameOrigin(
      { protocol: 'https:', hostname: 'example.org' },
      { protocol: 'https:', hostname: 'example.com' }
    ))
  })

  await t.test('file:// urls', (t) => {
    // urls with opaque origins should return true

    const a = new URL('file:///C:/undici')
    const b = new URL('file:///var/undici')

    t.assert.ok(util.sameOrigin(a, b))
  })
})

test('isURLPotentiallyTrustworthy', (t) => {
  // https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-let-localhost-be-localhost#section-5.2
  const valid = [
    'http://localhost',
    'http://localhost.',
    'http://127.0.0.1',
    'http://[::1]',
    'https://something.com',
    'wss://hello.com',
    'data:text/plain;base64,randomstring',
    'about:blank',
    'about:srcdoc',
    'http://subdomain.localhost',
    'http://subdomain.localhost.',
    'http://adb.localhost',
    'http://localhost.localhost',
    'blob:http://example.com/550e8400-e29b-41d4-a716-446655440000'
  ]
  const invalid = [
    'http://localhost.example.com',
    'http://subdomain.localhost.example.com',
    'file:///link/to/file.txt',
    'http://121.3.4.5:55',
    'null:8080',
    'something:8080'
  ]

  t.plan(valid.length + invalid.length + 1)
  t.assert.ok(!util.isURLPotentiallyTrustworthy('string'))

  for (const url of valid) {
    const instance = new URL(url)
    t.assert.ok(util.isURLPotentiallyTrustworthy(instance), instance)
  }

  for (const url of invalid) {
    const instance = new URL(url)
    t.assert.ok(!util.isURLPotentiallyTrustworthy(instance))
  }
})

describe('setRequestReferrerPolicyOnRedirect', () => {
  [
    [
      'should ignore empty string as policy',
      'origin, asdas, asdaw34, no-referrer,,',
      'no-referrer'
    ],
    [
      'should set referrer policy from response headers on redirect',
      'origin',
      'origin'
    ],
    [
      'should select the first valid policy from a response',
      'asdas, origin',
      'origin'
    ],
    [
      'should select the first valid policy from a response#2',
      'no-referrer, asdas, origin, 0943sd',
      'origin'
    ],
    [
      'should pick the last fallback over invalid policy tokens',
      'origin, asdas, asdaw34',
      'origin'
    ],
    [
      'should set not change request referrer policy if no Referrer-Policy from initial redirect response',
      null,
      'no-referrer, strict-origin-when-cross-origin'
    ],
    [
      'should set not change request referrer policy if the policy is a non-valid Referrer Policy',
      'asdasd',
      'no-referrer, strict-origin-when-cross-origin'
    ],
    [
      'should set not change request referrer policy if the policy is a non-valid Referrer Policy #2',
      'asdasd, asdasa, 12daw,',
      'no-referrer, strict-origin-when-cross-origin'
    ]
  ].forEach(([title, responseReferrerPolicy, expected]) => {
    test(title, (t) => {
      t.plan(1)

      const request = {
        referrerPolicy: 'no-referrer, strict-origin-when-cross-origin'
      }

      const actualResponse = {
        headersList: new HeadersList()
      }

      actualResponse.headersList.append('Connection', 'close')
      actualResponse.headersList.append('Location', 'https://some-location.com/redirect')
      if (responseReferrerPolicy) {
        actualResponse.headersList.append('Referrer-Policy', responseReferrerPolicy)
      }
      util.setRequestReferrerPolicyOnRedirect(request, actualResponse)

      t.assert.strictEqual(request.referrerPolicy, expected)
    })
  })
})

describe('urlHasHttpsScheme', () => {
  const { urlHasHttpsScheme } = util

  test('should return false for http url', (t) => {
    t.assert.strictEqual(urlHasHttpsScheme('http://example.com'), false)
  })
  test('should return true for https url', (t) => {
    t.assert.strictEqual(urlHasHttpsScheme('https://example.com'), true)
  })
  test('should return false for http object', (t) => {
    t.assert.strictEqual(urlHasHttpsScheme({ protocol: 'http:' }), false)
  })
  test('should return true for https object', (t) => {
    t.assert.strictEqual(urlHasHttpsScheme({ protocol: 'https:' }), true)
  })
})

describe('isValidHeaderValue', () => {
  const { isValidHeaderValue } = util

  test('should return true for valid string', (t) => {
    t.assert.strictEqual(isValidHeaderValue('valid123'), true)
    t.assert.strictEqual(isValidHeaderValue('va lid123'), true)
    t.assert.strictEqual(isValidHeaderValue('va\tlid123'), true)
  })
  test('should return false for string containing NUL', (t) => {
    t.assert.strictEqual(isValidHeaderValue('invalid\0'), false)
    t.assert.strictEqual(isValidHeaderValue('in\0valid'), false)
    t.assert.strictEqual(isValidHeaderValue('\0invalid'), false)
  })
  test('should return false for string containing CR', (t) => {
    t.assert.strictEqual(isValidHeaderValue('invalid\r'), false)
    t.assert.strictEqual(isValidHeaderValue('in\rvalid'), false)
    t.assert.strictEqual(isValidHeaderValue('\rinvalid'), false)
  })
  test('should return false for string containing LF', (t) => {
    t.assert.strictEqual(isValidHeaderValue('invalid\n'), false)
    t.assert.strictEqual(isValidHeaderValue('in\nvalid'), false)
    t.assert.strictEqual(isValidHeaderValue('\ninvalid'), false)
  })

  test('should return false for string with leading TAB', (t) => {
    t.assert.strictEqual(isValidHeaderValue('\tinvalid'), false)
  })
  test('should return false for string with trailing TAB', (t) => {
    t.assert.strictEqual(isValidHeaderValue('invalid\t'), false)
  })
  test('should return false for string with leading SPACE', (t) => {
    t.assert.strictEqual(isValidHeaderValue(' invalid'), false)
  })
  test('should return false for string with trailing SPACE', (t) => {
    t.assert.strictEqual(isValidHeaderValue('invalid '), false)
  })
})

describe('isOriginIPPotentiallyTrustworthy()', () => {
  [
    ['', false],
    ['0000:0000:0000:0000:0000:0000:0000:0001', true],
    ['0001:0000:0000:0000:0000:0000:0000:0001', false],
    ['0000:0000:0000:0000:0000:0000::0001', true],
    ['0001:0000:0000:0000:0000:0000::0001', false],
    ['0000:0000:0001:0000:0000:0000::0001', false],
    ['0000:0000:0000:0000:0000::0001', true],
    ['0000:0000:0000:0000::0001', true],
    ['0000:0000:0000::0001', true],
    ['0000:0000::0001', true],
    ['0000::0001', true],
    ['::1001', false],
    ['::0001', true],
    ['::0011', false],
    ['::1', true],
    ['[::1]', true],
    ['[::1', false],
    ['::1]', false],
    ['::2', false],
    ['::', false],
    ['126.0.0.0', false],
    ['127.0.0.0', true],
    ['127.0.0.1', true],
    ['127.255.255.255', true],
    ['128.255.255.255', false]
  ].forEach(([ip, expected]) => {
    test(`${ip} is ${expected ? '' : 'not '}potentially trustworthy`, (t) => {
      t.assert.strictEqual(util.isOriginIPPotentiallyTrustworthy(ip), expected)
    })
  })
})
