text: /* eslint-env jest */
  /* global browserName */
  import webdriver from 'next-webdriver'
  import { readFileSync } from 'fs'
  import http from 'http'
  import url from 'url'
  import { join } from 'path'
  import { getBrowserBodyText, waitFor, fetchViaHTTP } from 'next-test-utils'
  import { recursiveReadDir } from 'next/dist/lib/recursive-readdir'
  import { homedir } from 'os'
  
  // Does the same evaluation checking for INJECTED for 5 seconds after hydration, triggering every 500ms
  async function checkInjected(browser) {
    const start = Date.now()
    while (Date.now() - start < 5000) {
      const bodyText = await getBrowserBodyText(browser)
      if (/INJECTED/.test(bodyText)) {
        throw new Error('Vulnerable to XSS attacks')
      }
      await waitFor(500)
    }
  }
  
  module.exports = (context) => {
    describe('With Security Related Issues', () => {
      it('should handle invalid URL properly', async () => {
        async function invalidRequest() {
          return new Promise((resolve, reject) => {
            const request = http.request(
              {
                hostname: `localhost`,
                port: context.appPort,
                path: `*`,
              },
              (response) => {
                resolve(response.statusCode)
              }
            )
            request.on('error', (err) => reject(err))
            request.end()
          })
        }
        try {
          expect(await invalidRequest()).toBe(400)
          expect(await invalidRequest()).toBe(400)
        } catch (err) {
          // eslint-disable-next-line
          expect(err.code).toBe('ECONNREFUSED')
        }
      })
  
      it('should only access files inside .next directory', async () => {
        const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8')
  
        const pathsToCheck = [
          `/_next/${buildId}/page/../../../info`,
          `/_next/${buildId}/page/../../../info.js`,
          `/_next/${buildId}/page/../../../info.json`,
          `/_next/:buildId/webpack/chunks/../../../info.json`,
          `/_next/:buildId/webpack/../../../info.json`,
          `/_next/../../../info.json`,
          `/static/../../../info.json`,
          `/static/../info.json`,
          `/../../../info.json`,
          `/../../info.json`,
          `/../info.json`,
          `/info.json`,
        ]
  
        for (const path of pathsToCheck) {
          const res = await fetchViaHTTP(context.appPort, path)
          const data = await res.text()
          expect(data.includes('cool-version')).toBeFalsy()
          expect([400, 404].includes(res.status)).toBeTruthy()
        }
      })
  
      it('should not allow accessing files outside .next/static directory', async () => {
        const pathsToCheck = [
          `/_next/static/../server/pages-manifest.json`,
          `/_next/static/../server/build-manifest.json`,
          `/_next/static/../BUILD_ID`,
          `/_next/static/../routes-manifest.json`,
        ]
        for (const path of pathsToCheck) {
          const res = await fetchViaHTTP(context.appPort, path)
          const text = await res.text()
          try {
            expect(res.status).toBe(404)
            expect(text).toMatch(/This page could not be found/)
          } catch (err) {
            throw new Error(`Path ${path} accessible from the browser`)
          }
        }
      })
  
      it("should not leak the user's home directory into the build", async () => {
        const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8')
  
        const readPath = join(__dirname, `../.next/static/${buildId}`)
        const buildFiles = await recursiveReadDir(readPath, /\.js$/)
  
        if (buildFiles.length < 1) {
          throw new Error('Could not locate any build files')
        }
  
        const homeDir = homedir()
        buildFiles.forEach((buildFile) => {
          const content = readFileSync(join(readPath, buildFile), 'utf8')
          if (content.includes(homeDir)) {
            throw new Error(
              `Found the user's home directory in: ${buildFile}, ${homeDir}\n\n${content}`
            )
          }
          // TODO: this checks the monorepo's path currently, we should check
          // the Next.js apps directory instead once using isolated next
          const checkPathProject = join(__dirname, ...Array(4).fill('..'))
          if (
            content.includes(checkPathProject) ||
            (process.platform.match(/win/) &&
              content.includes(checkPathProject.replace(/\\/g, '\\\\')))
          ) {
            throw new Error(
              `Found the project path in: ${buildFile}, ${checkPathProject}\n\n${content}`
            )
          }
        })
      })
  
      it('should prevent URI based XSS attacks', async () => {
        const browser = await webdriver(
          context.appPort,
          '/\',document.body.innerHTML="INJECTED",\''
        )
        await checkInjected(browser)
        await browser.close()
      })
  
      it('should prevent URI based XSS attacks using single quotes', async () => {
        const browser = await webdriver(
          context.appPort,
          `/'-(document.body.innerHTML='INJECTED')-'`
        )
        await checkInjected(browser)
        await browser.close()
      })
  
      it('should prevent URI based XSS attacks using double quotes', async () => {
        const browser = await webdriver(
          context.appPort,
          `/"-(document.body.innerHTML='INJECTED')-"`
        )
        await checkInjected(browser)
  
        await browser.close()
      })
  
      it('should prevent URI based XSS attacks using semicolons and double quotes', async () => {
        const browser = await webdriver(
          context.appPort,
          `/;"-(document.body.innerHTML='INJECTED')-"`
        )
        await checkInjected(browser)
  
        await browser.close()
      })
  
      it('should prevent URI based XSS attacks using semicolons and single quotes', async () => {
        const browser = await webdriver(
          context.appPort,
          `/;'-(document.body.innerHTML='INJECTED')-'`
        )
        await checkInjected(browser)
  
        await browser.close()
      })
  
      it('should prevent URI based XSS attacks using src', async () => {
        const browser = await webdriver(
          context.appPort,
          `/javascript:(document.body.innerHTML='INJECTED')`
        )
        await checkInjected(browser)
  
        await browser.close()
      })
  
      it('should prevent URI based XSS attacks using querystring', async () => {
        const browser = await webdriver(
          context.appPort,
          `/?javascript=(document.body.innerHTML='INJECTED')`
        )
        await checkInjected(browser)
  
        await browser.close()
      })
  
      it('should prevent URI based XSS attacks using querystring and quotes', async () => {
        const browser = await webdriver(
          context.appPort,
          `/?javascript="(document.body.innerHTML='INJECTED')"`
        )
        await checkInjected(browser)
        await browser.close()
      })
  
      it('should handle encoded value in the pathname correctly \\', async () => {
        const res = await fetchViaHTTP(
          context.appPort,
          '/redirect/me/to-about/' + encodeURI('\\google.com'),
          undefined,
          {
            redirect: 'manual',
          }
        )
  
        const { pathname, hostname } = url.parse(
          res.headers.get('location') || ''
        )
        expect(res.status).toBe(307)
        expect(pathname).toBe(encodeURI('/\\google.com/about'))
        expect(hostname).toBe('localhost')
      })
  
      it('should handle encoded value in the pathname correctly %', async () => {
        const res = await fetchViaHTTP(
          context.appPort,
          '/redirect/me/to-about/%25google.com',
          undefined,
          {
            redirect: 'manual',
          }
        )
  
        const { pathname, hostname } = url.parse(
          res.headers.get('location') || ''
        )
        expect(res.status).toBe(307)
        expect(pathname).toBe('/%25google.com/about')
        expect(hostname).toBe('localhost')
      })
  
      it('should handle encoded value in the query correctly', async () => {
        const res = await fetchViaHTTP(
          context.appPort,
          '/trailing-redirect/?url=https%3A%2F%2Fgoogle.com%2Fimage%3Fcrop%3Dfocalpoint%26w%3D24&w=1200&q=100',
          undefined,
          {
            redirect: 'manual',
          }
        )
  
        const { pathname, hostname, query } = url.parse(
          res.headers.get('location') || ''
        )
        expect(res.status).toBe(308)
        expect(pathname).toBe('/trailing-redirect')
        expect(hostname).toBe('localhost')
        expect(query).toBe(
          'url=https%3A%2F%2Fgoogle.com%2Fimage%3Fcrop%3Dfocalpoint%26w%3D24&w=1200&q=100'
        )
      })
  
      it('should handle encoded value in the pathname correctly /', async () => {
        const res = await fetchViaHTTP(
          context.appPort,
          '/redirect/me/to-about/%2fgoogle.com',
          undefined,
          {
            redirect: 'manual',
          }
        )
  
        const { pathname, hostname } = url.parse(
          res.headers.get('location') || ''
        )
        expect(res.status).toBe(307)
        expect(pathname).toBe('/%2fgoogle.com/about')
        expect(hostname).not.toBe('google.com')
      })
  
      it('should handle encoded value in the pathname to query correctly (/)', async () => {
        const res = await fetchViaHTTP(
          context.appPort,
          '/redirect-query-test/%2Fgoogle.com',
          undefined,
          {
            redirect: 'manual',
          }
        )
  
        const { pathname, hostname, query } = url.parse(
          res.headers.get('location') || ''
        )
        expect(res.status).toBe(307)
        expect(pathname).toBe('/about')
        expect(query).toBe('foo=%2Fgoogle.com')
        expect(hostname).not.toBe('google.com')
        expect(hostname).not.toMatch(/google/)
      })
  
      it('should handle encoded / value for trailing slash correctly', async () => {
        const res = await fetchViaHTTP(
          context.appPort,
          '/%2fexample.com/',
          undefined,
          { redirect: 'manual' }
        )
  
        const { pathname, hostname } = url.parse(
          res.headers.get('location') || ''
        )
        expect(res.status).toBe(308)
        expect(pathname).toBe('/%2fexample.com')
        expect(hostname).not.toBe('example.com')
      })
  
      if (browserName !== 'internet explorer') {
        it('should not execute script embedded inside svg image, even if dangerouslyAllowSVG=true', async () => {
          let browser
          try {
            browser = await webdriver(context.appPort, '/svg-image')
            await browser.eval(`document.getElementById("img").scrollIntoView()`)
            expect(
              await browser.elementById('img').getAttribute('src')
            ).toContain('xss.svg')
            expect(await browser.elementById('msg').text()).toBe('safe')
            browser = await webdriver(
              context.appPort,
              '/_next/image?url=%2Fxss.svg&w=256&q=75'
            )
            expect(await browser.elementById('msg').text()).toBe('safe')
          } finally {
            if (browser) await browser.close()
          }
        })
      }
    })
  }
  