CVE-2021-43803: Insufficient Information (NVD-CWE-noinfo)
value: Next.js is a React framework. In versions of Next.js prior to 12.0.5 or 11.1.3, invalid or malformed URLs could lead to a server crash. In order to be affected by this issue, the deployment must use Next.js versions above 11.1.0 and below 12.0.5, Node.js above 15.0.0, and next start or a custom server. Deployments on Vercel are not affected, along with similar environments where invalid requests are filtered before reaching Next.js. Versions 12.0.5 and 11.1.3 contain patches for this issue.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 7.5
Published: 2021-12-10T00:15Z
Repository: https://github.com/vercel/next.js
Language: JavaScript
Files changed: packages/next/build/swc/index.js, packages/next/server/next-server.ts, test/integration/production/test/security.js
Commit date: 2021-12-03 17:31:09 +0000
Commit message: Ensure invalid URLs respond with 400 correctly (#32092)

This ensures we catch any errors in `handleRequest` so that we can respond with a 400 for invalid requests. 

## Bug

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

Fixes: https://github.com/vercel/next.js/issues/32075
Closes: https://github.com/vercel/next.js/pull/32080
Diff: diff --git a/packages/next/build/swc/index.js b/packages/next/build/swc/index.js
index 07219299c..85b2e35d3 100644
--- a/packages/next/build/swc/index.js
+++ b/packages/next/build/swc/index.js
@@ -1,8 +1,8 @@
 import { platform, arch } from 'os'
 import { platformArchTriples } from '@napi-rs/triples'
-import Log from '../output/log'
+import * as Log from '../output/log'
 
 const ArchName = arch()
 const PlatformName = platform()
 const triples = platformArchTriples[PlatformName][ArchName] || []
 
diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts
index 83c0bdfd8..88b3f6326 100644
--- a/packages/next/server/next-server.ts
+++ b/packages/next/server/next-server.ts
@@ -357,210 +357,218 @@ export default class Server {
   private async handleRequest(
     req: IncomingMessage,
     res: ServerResponse,
     parsedUrl?: NextUrlWithParsedQuery
   ): Promise<void> {
-    const urlParts = (req.url || '').split('?')
-    const urlNoQuery = urlParts[0]
-
-    if (urlNoQuery?.match(/(\\|\/\/)/)) {
-      const cleanUrl = normalizeRepeatedSlashes(req.url!)
-      res.setHeader('Location', cleanUrl)
-      res.setHeader('Refresh', `0;url=${cleanUrl}`)
-      res.statusCode = 308
-      res.end(cleanUrl)
-      return
-    }
+    try {
+      const urlParts = (req.url || '').split('?')
+      const urlNoQuery = urlParts[0]
+
+      if (urlNoQuery?.match(/(\\|\/\/)/)) {
+        const cleanUrl = normalizeRepeatedSlashes(req.url!)
+        res.setHeader('Location', cleanUrl)
+        res.setHeader('Refresh', `0;url=${cleanUrl}`)
+        res.statusCode = 308
+        res.end(cleanUrl)
+        return
+      }
 
-    setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers))
+      setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers))
 
-    // Parse url if parsedUrl not provided
-    if (!parsedUrl || typeof parsedUrl !== 'object') {
-      parsedUrl = parseUrl(req.url!, true)
-    }
+      // Parse url if parsedUrl not provided
+      if (!parsedUrl || typeof parsedUrl !== 'object') {
+        parsedUrl = parseUrl(req.url!, true)
+      }
 
-    // Parse the querystring ourselves if the user doesn't handle querystring parsing
-    if (typeof parsedUrl.query === 'string') {
-      parsedUrl.query = parseQs(parsedUrl.query)
-    }
+      // Parse the querystring ourselves if the user doesn't handle querystring parsing
+      if (typeof parsedUrl.query === 'string') {
+        parsedUrl.query = parseQs(parsedUrl.query)
+      }
 
-    // When there are hostname and port we build an absolute URL
-    const initUrl =
-      this.hostname && this.port
-        ? `http://${this.hostname}:${this.port}${req.url}`
-        : req.url
+      // When there are hostname and port we build an absolute URL
+      const initUrl =
+        this.hostname && this.port
+          ? `http://${this.hostname}:${this.port}${req.url}`
+          : req.url
 
-    addRequestMeta(req, '__NEXT_INIT_URL', initUrl)
-    addRequestMeta(req, '__NEXT_INIT_QUERY', { ...parsedUrl.query })
+      addRequestMeta(req, '__NEXT_INIT_URL', initUrl)
+      addRequestMeta(req, '__NEXT_INIT_QUERY', { ...parsedUrl.query })
 
-    const url = parseNextUrl({
-      headers: req.headers,
-      nextConfig: this.nextConfig,
-      url: req.url?.replace(/^\/+/, '/'),
-    })
+      const url = parseNextUrl({
+        headers: req.headers,
+        nextConfig: this.nextConfig,
+        url: req.url?.replace(/^\/+/, '/'),
+      })
 
-    if (url.basePath) {
-      req.url = replaceBasePath(req.url!, this.nextConfig.basePath)
-      addRequestMeta(req, '_nextHadBasePath', true)
-    }
+      if (url.basePath) {
+        req.url = replaceBasePath(req.url!, this.nextConfig.basePath)
+        addRequestMeta(req, '_nextHadBasePath', true)
+      }
 
-    if (
-      this.minimalMode &&
-      req.headers['x-matched-path'] &&
-      typeof req.headers['x-matched-path'] === 'string'
-    ) {
-      const reqUrlIsDataUrl = req.url?.includes('/_next/data')
-      const matchedPathIsDataUrl =
-        req.headers['x-matched-path']?.includes('/_next/data')
-      const isDataUrl = reqUrlIsDataUrl || matchedPathIsDataUrl
-
-      let parsedPath = parseUrl(
-        isDataUrl ? req.url! : (req.headers['x-matched-path'] as string),
-        true
-      )
+      if (
+        this.minimalMode &&
+        req.headers['x-matched-path'] &&
+        typeof req.headers['x-matched-path'] === 'string'
+      ) {
+        const reqUrlIsDataUrl = req.url?.includes('/_next/data')
+        const matchedPathIsDataUrl =
+          req.headers['x-matched-path']?.includes('/_next/data')
+        const isDataUrl = reqUrlIsDataUrl || matchedPathIsDataUrl
+
+        let parsedPath = parseUrl(
+          isDataUrl ? req.url! : (req.headers['x-matched-path'] as string),
+          true
+        )
 
-      let matchedPathname = parsedPath.pathname!
+        let matchedPathname = parsedPath.pathname!
 
-      let matchedPathnameNoExt = isDataUrl
-        ? matchedPathname.replace(/\.json$/, '')
-        : matchedPathname
+        let matchedPathnameNoExt = isDataUrl
+          ? matchedPathname.replace(/\.json$/, '')
+          : matchedPathname
 
-      if (this.nextConfig.i18n) {
-        const localePathResult = normalizeLocalePath(
-          matchedPathname || '/',
-          this.nextConfig.i18n.locales
-        )
+        if (this.nextConfig.i18n) {
+          const localePathResult = normalizeLocalePath(
+            matchedPathname || '/',
+            this.nextConfig.i18n.locales
+          )
 
-        if (localePathResult.detectedLocale) {
-          parsedUrl.query.__nextLocale = localePathResult.detectedLocale
+          if (localePathResult.detectedLocale) {
+            parsedUrl.query.__nextLocale = localePathResult.detectedLocale
+          }
         }
-      }
-
-      if (isDataUrl) {
-        matchedPathname = denormalizePagePath(matchedPathname)
-        matchedPathnameNoExt = denormalizePagePath(matchedPathnameNoExt)
-      }
 
-      const pageIsDynamic = isDynamicRoute(matchedPathnameNoExt)
-      const combinedRewrites: Rewrite[] = []
+        if (isDataUrl) {
+          matchedPathname = denormalizePagePath(matchedPathname)
+          matchedPathnameNoExt = denormalizePagePath(matchedPathnameNoExt)
+        }
 
-      combinedRewrites.push(...this.customRoutes.rewrites.beforeFiles)
-      combinedRewrites.push(...this.customRoutes.rewrites.afterFiles)
-      combinedRewrites.push(...this.customRoutes.rewrites.fallback)
+        const pageIsDynamic = isDynamicRoute(matchedPathnameNoExt)
+        const combinedRewrites: Rewrite[] = []
 
-      const utils = getUtils({
-        pageIsDynamic,
-        page: matchedPathnameNoExt,
-        i18n: this.nextConfig.i18n,
-        basePath: this.nextConfig.basePath,
-        rewrites: combinedRewrites,
-      })
+        combinedRewrites.push(...this.customRoutes.rewrites.beforeFiles)
+        combinedRewrites.push(...this.customRoutes.rewrites.afterFiles)
+        combinedRewrites.push(...this.customRoutes.rewrites.fallback)
 
-      try {
-        // ensure parsedUrl.pathname includes URL before processing
-        // rewrites or they won't match correctly
-        if (this.nextConfig.i18n && !url.locale?.path.detectedLocale) {
-          parsedUrl.pathname = `/${url.locale?.locale}${parsedUrl.pathname}`
-        }
-        utils.handleRewrites(req, parsedUrl)
+        const utils = getUtils({
+          pageIsDynamic,
+          page: matchedPathnameNoExt,
+          i18n: this.nextConfig.i18n,
+          basePath: this.nextConfig.basePath,
+          rewrites: combinedRewrites,
+        })
 
-        // interpolate dynamic params and normalize URL if needed
-        if (pageIsDynamic) {
-          let params: ParsedUrlQuery | false = {}
+        try {
+          // ensure parsedUrl.pathname includes URL before processing
+          // rewrites or they won't match correctly
+          if (this.nextConfig.i18n && !url.locale?.path.detectedLocale) {
+            parsedUrl.pathname = `/${url.locale?.locale}${parsedUrl.pathname}`
+          }
+          utils.handleRewrites(req, parsedUrl)
 
-          Object.assign(parsedUrl.query, parsedPath.query)
-          const paramsResult = utils.normalizeDynamicRouteParams(
-            parsedUrl.query
-          )
+          // interpolate dynamic params and normalize URL if needed
+          if (pageIsDynamic) {
+            let params: ParsedUrlQuery | false = {}
 
-          if (paramsResult.hasValidParams) {
-            params = paramsResult.params
-          } else if (req.headers['x-now-route-matches']) {
-            const opts: Record<string, string> = {}
-            params = utils.getParamsFromRouteMatches(
-              req,
-              opts,
-              parsedUrl.query.__nextLocale || ''
+            Object.assign(parsedUrl.query, parsedPath.query)
+            const paramsResult = utils.normalizeDynamicRouteParams(
+              parsedUrl.query
             )
 
-            if (opts.locale) {
-              parsedUrl.query.__nextLocale = opts.locale
+            if (paramsResult.hasValidParams) {
+              params = paramsResult.params
+            } else if (req.headers['x-now-route-matches']) {
+              const opts: Record<string, string> = {}
+              params = utils.getParamsFromRouteMatches(
+                req,
+                opts,
+                parsedUrl.query.__nextLocale || ''
+              )
+
+              if (opts.locale) {
+                parsedUrl.query.__nextLocale = opts.locale
+              }
+            } else {
+              params = utils.dynamicRouteMatcher!(matchedPathnameNoExt)
+            }
+
+            if (params) {
+              params = utils.normalizeDynamicRouteParams(params).params
+
+              matchedPathname = utils.interpolateDynamicPath(
+                matchedPathname,
+                params
+              )
+              req.url = utils.interpolateDynamicPath(req.url!, params)
             }
-          } else {
-            params = utils.dynamicRouteMatcher!(matchedPathnameNoExt)
-          }
 
-          if (params) {
-            params = utils.normalizeDynamicRouteParams(params).params
+            if (reqUrlIsDataUrl && matchedPathIsDataUrl) {
+              req.url = formatUrl({
+                ...parsedPath,
+                pathname: matchedPathname,
+              })
+            }
 
-            matchedPathname = utils.interpolateDynamicPath(
-              matchedPathname,
-              params
-            )
-            req.url = utils.interpolateDynamicPath(req.url!, params)
+            Object.assign(parsedUrl.query, params)
+            utils.normalizeVercelUrl(req, true)
           }
-
-          if (reqUrlIsDataUrl && matchedPathIsDataUrl) {
-            req.url = formatUrl({
-              ...parsedPath,
-              pathname: matchedPathname,
-            })
+        } catch (err) {
+          if (err instanceof DecodeError) {
+            res.statusCode = 400
+            return this.renderError(null, req, res, '/_error', {})
           }
-
-          Object.assign(parsedUrl.query, params)
-          utils.normalizeVercelUrl(req, true)
-        }
-      } catch (err) {
-        if (err instanceof DecodeError) {
-          res.statusCode = 400
-          return this.renderError(null, req, res, '/_error', {})
+          throw err
         }
-        throw err
-      }
 
-      parsedUrl.pathname = `${this.nextConfig.basePath || ''}${
-        matchedPathname === '/' && this.nextConfig.basePath
-          ? ''
-          : matchedPathname
-      }`
-      url.pathname = parsedUrl.pathname
-    }
+        parsedUrl.pathname = `${this.nextConfig.basePath || ''}${
+          matchedPathname === '/' && this.nextConfig.basePath
+            ? ''
+            : matchedPathname
+        }`
+        url.pathname = parsedUrl.pathname
+      }
 
-    addRequestMeta(req, '__nextHadTrailingSlash', url.locale?.trailingSlash)
-    if (url.locale?.domain) {
-      addRequestMeta(req, '__nextIsLocaleDomain', true)
-    }
+      addRequestMeta(req, '__nextHadTrailingSlash', url.locale?.trailingSlash)
+      if (url.locale?.domain) {
+        addRequestMeta(req, '__nextIsLocaleDomain', true)
+      }
 
-    if (url.locale?.path.detectedLocale) {
-      req.url = formatUrl(url)
-      addRequestMeta(req, '__nextStrippedLocale', true)
-      if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {
-        return this.render404(req, res, parsedUrl)
+      if (url.locale?.path.detectedLocale) {
+        req.url = formatUrl(url)
+        addRequestMeta(req, '__nextStrippedLocale', true)
+        if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {
+          return this.render404(req, res, parsedUrl)
+        }
       }
-    }
 
-    if (!this.minimalMode || !parsedUrl.query.__nextLocale) {
-      if (url?.locale?.locale) {
-        parsedUrl.query.__nextLocale = url.locale.locale
+      if (!this.minimalMode || !parsedUrl.query.__nextLocale) {
+        if (url?.locale?.locale) {
+          parsedUrl.query.__nextLocale = url.locale.locale
+        }
       }
-    }
 
-    if (url?.locale?.defaultLocale) {
-      parsedUrl.query.__nextDefaultLocale = url.locale.defaultLocale
-    }
+      if (url?.locale?.defaultLocale) {
+        parsedUrl.query.__nextDefaultLocale = url.locale.defaultLocale
+      }
 
-    if (url.locale?.redirect) {
-      res.setHeader('Location', url.locale.redirect)
-      res.statusCode = TEMPORARY_REDIRECT_STATUS
-      res.end()
-      return
-    }
+      if (url.locale?.redirect) {
+        res.setHeader('Location', url.locale.redirect)
+        res.statusCode = TEMPORARY_REDIRECT_STATUS
+        res.end()
+        return
+      }
 
-    res.statusCode = 200
-    try {
+      res.statusCode = 200
       return await this.run(req, res, parsedUrl)
-    } catch (err) {
+    } catch (err: any) {
+      if (
+        (err && typeof err === 'object' && err.code === 'ERR_INVALID_URL') ||
+        err instanceof DecodeError
+      ) {
+        res.statusCode = 400
+        return this.renderError(null, req, res, '/_error', {})
+      }
+
       if (this.minimalMode || this.renderOpts.dev) {
         throw err
       }
       this.logError(isError(err) ? err : new Error(err + ''))
       res.statusCode = 500
diff --git a/test/integration/production/test/security.js b/test/integration/production/test/security.js
index 5d1c2f880..6ce693ef8 100644
--- a/test/integration/production/test/security.js
+++ b/test/integration/production/test/security.js
@@ -1,9 +1,10 @@
 /* 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 {
   renderViaHTTP,
   getBrowserBodyText,
@@ -25,10 +26,36 @@ async function checkInjected(browser) {
   }
 }
 
 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`,

Vulnerable code: import Log from '../output/log'
    const urlParts = (req.url || '').split('?')
    const urlNoQuery = urlParts[0]

    if (urlNoQuery?.match(/(\\|\/\/)/)) {
      const cleanUrl = normalizeRepeatedSlashes(req.url!)
      res.setHeader('Location', cleanUrl)
      res.setHeader('Refresh', `0;url=${cleanUrl}`)
      res.statusCode = 308
      res.end(cleanUrl)
      return
    }
    setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers))
    // Parse url if parsedUrl not provided
    if (!parsedUrl || typeof parsedUrl !== 'object') {
      parsedUrl = parseUrl(req.url!, true)
    }
    // Parse the querystring ourselves if the user doesn't handle querystring parsing
    if (typeof parsedUrl.query === 'string') {
      parsedUrl.query = parseQs(parsedUrl.query)
    }
    // When there are hostname and port we build an absolute URL
    const initUrl =
      this.hostname && this.port
        ? `http://${this.hostname}:${this.port}${req.url}`
        : req.url
    addRequestMeta(req, '__NEXT_INIT_URL', initUrl)
    addRequestMeta(req, '__NEXT_INIT_QUERY', { ...parsedUrl.query })
    const url = parseNextUrl({
      headers: req.headers,
      nextConfig: this.nextConfig,
      url: req.url?.replace(/^\/+/, '/'),
    })
    if (url.basePath) {
      req.url = replaceBasePath(req.url!, this.nextConfig.basePath)
      addRequestMeta(req, '_nextHadBasePath', true)
    }
    if (
      this.minimalMode &&
      req.headers['x-matched-path'] &&
      typeof req.headers['x-matched-path'] === 'string'
    ) {
      const reqUrlIsDataUrl = req.url?.includes('/_next/data')
      const matchedPathIsDataUrl =
        req.headers['x-matched-path']?.includes('/_next/data')
      const isDataUrl = reqUrlIsDataUrl || matchedPathIsDataUrl

      let parsedPath = parseUrl(
        isDataUrl ? req.url! : (req.headers['x-matched-path'] as string),
        true
      )
      let matchedPathname = parsedPath.pathname!
      let matchedPathnameNoExt = isDataUrl
        ? matchedPathname.replace(/\.json$/, '')
        : matchedPathname
      if (this.nextConfig.i18n) {
        const localePathResult = normalizeLocalePath(
          matchedPathname || '/',
          this.nextConfig.i18n.locales
        )
        if (localePathResult.detectedLocale) {
          parsedUrl.query.__nextLocale = localePathResult.detectedLocale
      }

      if (isDataUrl) {
        matchedPathname = denormalizePagePath(matchedPathname)
        matchedPathnameNoExt = denormalizePagePath(matchedPathnameNoExt)
      }
      const pageIsDynamic = isDynamicRoute(matchedPathnameNoExt)
      const combinedRewrites: Rewrite[] = []
      combinedRewrites.push(...this.customRoutes.rewrites.beforeFiles)
      combinedRewrites.push(...this.customRoutes.rewrites.afterFiles)
      combinedRewrites.push(...this.customRoutes.rewrites.fallback)
      const utils = getUtils({
        pageIsDynamic,
        page: matchedPathnameNoExt,
        i18n: this.nextConfig.i18n,
        basePath: this.nextConfig.basePath,
        rewrites: combinedRewrites,
      })
      try {
        // ensure parsedUrl.pathname includes URL before processing
        // rewrites or they won't match correctly
        if (this.nextConfig.i18n && !url.locale?.path.detectedLocale) {
          parsedUrl.pathname = `/${url.locale?.locale}${parsedUrl.pathname}`
        }
        utils.handleRewrites(req, parsedUrl)
        // interpolate dynamic params and normalize URL if needed
        if (pageIsDynamic) {
          let params: ParsedUrlQuery | false = {}
          Object.assign(parsedUrl.query, parsedPath.query)
          const paramsResult = utils.normalizeDynamicRouteParams(
            parsedUrl.query
          )
          if (paramsResult.hasValidParams) {
            params = paramsResult.params
          } else if (req.headers['x-now-route-matches']) {
            const opts: Record<string, string> = {}
            params = utils.getParamsFromRouteMatches(
              req,
              opts,
              parsedUrl.query.__nextLocale || ''
            if (opts.locale) {
              parsedUrl.query.__nextLocale = opts.locale
          } else {
            params = utils.dynamicRouteMatcher!(matchedPathnameNoExt)
          }
          if (params) {
            params = utils.normalizeDynamicRouteParams(params).params
            matchedPathname = utils.interpolateDynamicPath(
              matchedPathname,
              params
            )
            req.url = utils.interpolateDynamicPath(req.url!, params)

          if (reqUrlIsDataUrl && matchedPathIsDataUrl) {
            req.url = formatUrl({
              ...parsedPath,
              pathname: matchedPathname,
            })

          Object.assign(parsedUrl.query, params)
          utils.normalizeVercelUrl(req, true)
        }
      } catch (err) {
        if (err instanceof DecodeError) {
          res.statusCode = 400
          return this.renderError(null, req, res, '/_error', {})
        throw err
      }
      parsedUrl.pathname = `${this.nextConfig.basePath || ''}${
        matchedPathname === '/' && this.nextConfig.basePath
          ? ''
          : matchedPathname
      }`
      url.pathname = parsedUrl.pathname
    }
    addRequestMeta(req, '__nextHadTrailingSlash', url.locale?.trailingSlash)
    if (url.locale?.domain) {
      addRequestMeta(req, '__nextIsLocaleDomain', true)
    }
    if (url.locale?.path.detectedLocale) {
      req.url = formatUrl(url)
      addRequestMeta(req, '__nextStrippedLocale', true)
      if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {
        return this.render404(req, res, parsedUrl)
    }
    if (!this.minimalMode || !parsedUrl.query.__nextLocale) {
      if (url?.locale?.locale) {
        parsedUrl.query.__nextLocale = url.locale.locale
    }
    if (url?.locale?.defaultLocale) {
      parsedUrl.query.__nextDefaultLocale = url.locale.defaultLocale
    }
    if (url.locale?.redirect) {
      res.setHeader('Location', url.locale.redirect)
      res.statusCode = TEMPORARY_REDIRECT_STATUS
      res.end()
      return
    }
    res.statusCode = 200
    try {
    } catch (err) {
Fixed code: import * as Log from '../output/log'
    try {
      const urlParts = (req.url || '').split('?')
      const urlNoQuery = urlParts[0]

      if (urlNoQuery?.match(/(\\|\/\/)/)) {
        const cleanUrl = normalizeRepeatedSlashes(req.url!)
        res.setHeader('Location', cleanUrl)
        res.setHeader('Refresh', `0;url=${cleanUrl}`)
        res.statusCode = 308
        res.end(cleanUrl)
        return
      }
      setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers))
      // Parse url if parsedUrl not provided
      if (!parsedUrl || typeof parsedUrl !== 'object') {
        parsedUrl = parseUrl(req.url!, true)
      }
      // Parse the querystring ourselves if the user doesn't handle querystring parsing
      if (typeof parsedUrl.query === 'string') {
        parsedUrl.query = parseQs(parsedUrl.query)
      }
      // When there are hostname and port we build an absolute URL
      const initUrl =
        this.hostname && this.port
          ? `http://${this.hostname}:${this.port}${req.url}`
          : req.url
      addRequestMeta(req, '__NEXT_INIT_URL', initUrl)
      addRequestMeta(req, '__NEXT_INIT_QUERY', { ...parsedUrl.query })
      const url = parseNextUrl({
        headers: req.headers,
        nextConfig: this.nextConfig,
        url: req.url?.replace(/^\/+/, '/'),
      })
      if (url.basePath) {
        req.url = replaceBasePath(req.url!, this.nextConfig.basePath)
        addRequestMeta(req, '_nextHadBasePath', true)
      }
      if (
        this.minimalMode &&
        req.headers['x-matched-path'] &&
        typeof req.headers['x-matched-path'] === 'string'
      ) {
        const reqUrlIsDataUrl = req.url?.includes('/_next/data')
        const matchedPathIsDataUrl =
          req.headers['x-matched-path']?.includes('/_next/data')
        const isDataUrl = reqUrlIsDataUrl || matchedPathIsDataUrl

        let parsedPath = parseUrl(
          isDataUrl ? req.url! : (req.headers['x-matched-path'] as string),
          true
        )
        let matchedPathname = parsedPath.pathname!
        let matchedPathnameNoExt = isDataUrl
          ? matchedPathname.replace(/\.json$/, '')
          : matchedPathname
        if (this.nextConfig.i18n) {
          const localePathResult = normalizeLocalePath(
            matchedPathname || '/',
            this.nextConfig.i18n.locales
          )
          if (localePathResult.detectedLocale) {
            parsedUrl.query.__nextLocale = localePathResult.detectedLocale
          }
        if (isDataUrl) {
          matchedPathname = denormalizePagePath(matchedPathname)
          matchedPathnameNoExt = denormalizePagePath(matchedPathnameNoExt)
        }
        const pageIsDynamic = isDynamicRoute(matchedPathnameNoExt)
        const combinedRewrites: Rewrite[] = []
        combinedRewrites.push(...this.customRoutes.rewrites.beforeFiles)
        combinedRewrites.push(...this.customRoutes.rewrites.afterFiles)
        combinedRewrites.push(...this.customRoutes.rewrites.fallback)
        const utils = getUtils({
          pageIsDynamic,
          page: matchedPathnameNoExt,
          i18n: this.nextConfig.i18n,
          basePath: this.nextConfig.basePath,
          rewrites: combinedRewrites,
        })
        try {
          // ensure parsedUrl.pathname includes URL before processing
          // rewrites or they won't match correctly
          if (this.nextConfig.i18n && !url.locale?.path.detectedLocale) {
            parsedUrl.pathname = `/${url.locale?.locale}${parsedUrl.pathname}`
          }
          utils.handleRewrites(req, parsedUrl)
          // interpolate dynamic params and normalize URL if needed
          if (pageIsDynamic) {
            let params: ParsedUrlQuery | false = {}
            Object.assign(parsedUrl.query, parsedPath.query)
            const paramsResult = utils.normalizeDynamicRouteParams(
              parsedUrl.query
            if (paramsResult.hasValidParams) {
              params = paramsResult.params
            } else if (req.headers['x-now-route-matches']) {
              const opts: Record<string, string> = {}
              params = utils.getParamsFromRouteMatches(
                req,
                opts,
                parsedUrl.query.__nextLocale || ''
              )

              if (opts.locale) {
                parsedUrl.query.__nextLocale = opts.locale
              }
            } else {
              params = utils.dynamicRouteMatcher!(matchedPathnameNoExt)
            }

            if (params) {
              params = utils.normalizeDynamicRouteParams(params).params

              matchedPathname = utils.interpolateDynamicPath(
                matchedPathname,
                params
              )
              req.url = utils.interpolateDynamicPath(req.url!, params)
            if (reqUrlIsDataUrl && matchedPathIsDataUrl) {
              req.url = formatUrl({
                ...parsedPath,
                pathname: matchedPathname,
              })
            }
            Object.assign(parsedUrl.query, params)
            utils.normalizeVercelUrl(req, true)
        } catch (err) {
          if (err instanceof DecodeError) {
            res.statusCode = 400
            return this.renderError(null, req, res, '/_error', {})
          throw err
        parsedUrl.pathname = `${this.nextConfig.basePath || ''}${
          matchedPathname === '/' && this.nextConfig.basePath
            ? ''
            : matchedPathname
        }`
        url.pathname = parsedUrl.pathname
      }
      addRequestMeta(req, '__nextHadTrailingSlash', url.locale?.trailingSlash)
      if (url.locale?.domain) {
        addRequestMeta(req, '__nextIsLocaleDomain', true)
      }
      if (url.locale?.path.detectedLocale) {
        req.url = formatUrl(url)
        addRequestMeta(req, '__nextStrippedLocale', true)
        if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {
          return this.render404(req, res, parsedUrl)
        }
      if (!this.minimalMode || !parsedUrl.query.__nextLocale) {
        if (url?.locale?.locale) {
          parsedUrl.query.__nextLocale = url.locale.locale
        }
      if (url?.locale?.defaultLocale) {
        parsedUrl.query.__nextDefaultLocale = url.locale.defaultLocale
      }
      if (url.locale?.redirect) {
        res.setHeader('Location', url.locale.redirect)
        res.statusCode = TEMPORARY_REDIRECT_STATUS
        res.end()
        return
      }
      res.statusCode = 200
    } catch (err: any) {
      if (
        (err && typeof err === 'object' && err.code === 'ERR_INVALID_URL') ||
        err instanceof DecodeError
      ) {
        res.statusCode = 400
        return this.renderError(null, req, res, '/_error', {})
      }

import http from 'http'
    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')
      }
    })