nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
67.69k stars 7.63k forks source link

Invalid URL type error crashes app #12381

Closed dbulic-margins closed 1 year ago

dbulic-margins commented 1 year ago

Is there an existing issue for this?

Current behavior

I have reconnecting WebSocket on the frontend that is trying to connect to the WebSocket gateway on ws://localhost:3000/ . Mistakenly the URL requested is ws://localhost:3000// which is invalid URL (because of two slashes at the end) and platform-ws throws TypeError that crashes entire backend app:

TypeError: Invalid URL
    at new NodeError (node:internal/errors:405:5)
    at new URL (node:internal/url:611:13)
    at Server.<anonymous> (/Users/****/projects/****/node_modules/@nestjs/platform-ws/adapters/ws-adapter.js:108:30)
    at Server.emit (node:events:514:28)
    at onParserExecuteCommon (node:_http_server:915:14)
    at onParserExecute (node:_http_server:809:3)

Minimum reproduction code

https://github.com/nestjs/nest/blob/5bba7e9d264319490f142ca5e8099c559fa7e7e3/packages/platform-ws/adapters/ws-adapter.ts#L182

Steps to reproduce

No response

Expected behavior

URL parsing should have some fallback action, or error handling. const pathname = new URL(request.url, baseUrl).pathname; https://github.com/nestjs/nest/blob/5bba7e9d264319490f142ca5e8099c559fa7e7e3/packages/platform-ws/adapters/ws-adapter.ts#L182

Package

Other package

No response

NestJS version

9.5.0

Packages versions

{
  "name": "project_name",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "engines": {
    "node": ">=18.0.0"
  },
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/src/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json",
    "typeorm": "npx typeorm -d dist/database/ormconfig.js",
    "typeorm:run-migrations": "npm run typeorm -- migration:run",
    "typeorm:generate-migration": "npm run typeorm -- migration:generate database/migrations/$npm_config_name",
    "typeorm:create-migration": "npm run typeorm -- migration:create database/migrations/$npm_config_name",
    "typeorm:revert-migration": "npm run typeorm -- migration:revert",
    "typeorm:show-migrations": "npm run typeorm -- migration:show",
    "typeorm:drop-schema": "npm run typeorm -- schema:drop",
    "typeorm:sync-schema": "npm run typeorm -- schema:sync",
    "typeorm:log-schema": "npm run typeorm -- schema:log",
    "prepare": "husky install",
    "postinstall": "npm run build"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.113.0",
    "@aws-sdk/lib-storage": "^3.117.0",
    "@nestjs/common": "^9.4.2",
    "@nestjs/config": "^2.1.0",
    "@nestjs/core": "^9.4.2",
    "@nestjs/mapped-types": "*",
    "@nestjs/mongoose": "^9.2.0",
    "@nestjs/passport": "^9.0.3",
    "@nestjs/platform-express": "^9.4.2",
    "@nestjs/platform-socket.io": "^9.4.2",
    "@nestjs/platform-ws": "^9.4.2",
    "@nestjs/schedule": "^2.1.0",
    "@nestjs/swagger": "^6.3.0",
    "@nestjs/throttler": "^4.0.0",
    "@nestjs/typeorm": "^9.0.1",
    "@nestjs/websockets": "^9.4.2",
    "@sendgrid/mail": "^7.7.0",
    "@socket.io/redis-adapter": "^7.2.0",
    "@teamwork/websocket-json-stream": "^2.0.0",
    "@types/sharedb": "^3.2.1",
    "aws-sdk": "^2.1163.0",
    "axios": "^1.4.0",
    "bcrypt": "^5.0.1",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.14.0",
    "cookie-parser": "^1.4.6",
    "dotenv": "^16.0.1",
    "getstream": "^8.1.2",
    "gpt-3-encoder": "^1.1.4",
    "helmet": "^7.0.0",
    "jsonapi-serializer": "^3.6.7",
    "jsonwebtoken": "^9.0.0",
    "lodash": "^4.17.21",
    "mongoose": "^6.8.4",
    "nest-winston": "^1.6.2",
    "openai": "^3.2.1",
    "ot-json1": "^1.0.2",
    "passport": "^0.6.0",
    "passport-azure-ad": "^4.3.3",
    "passport-google-oauth20": "^2.0.0",
    "pg": "^8.7.3",
    "redis": "^4.2.0",
    "reflect-metadata": "^0.1.13",
    "rich-text": "^4.1.0",
    "rimraf": "^3.0.2",
    "rxjs": "^7.2.0",
    "sharedb": "^3.2.1",
    "sharedb-milestone-mongo": "^0.8.0",
    "sharedb-mongo": "^2.0.0",
    "sharedb-redis-pubsub": "^3.0.0",
    "sharp": "^0.32.1",
    "socket.io": "^4.5.1",
    "stream-chat": "^8.4.1",
    "swagger-ui-express": "^4.4.0",
    "typeorm": "^0.3.16",
    "unique-names-generator": "^4.7.1",
    "uuidv4": "^6.2.13",
    "winston": "^3.7.2",
    "winston-daily-rotate-file": "^4.7.1"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.5.0",
    "@nestjs/schematics": "^8.0.0",
    "@nestjs/testing": "^9.4.2",
    "@types/bcrypt": "^5.0.0",
    "@types/cookie-parser": "^1.4.3",
    "@types/cron": "^2.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "27.4.1",
    "@types/jsonapi-serializer": "^3.6.5",
    "@types/jsonwebtoken": "^8.5.8",
    "@types/lodash": "^4.14.182",
    "@types/multer": "^1.4.7",
    "@types/node": "^16.18.8",
    "@types/passport-azure-ad": "^4.3.1",
    "@types/passport-google-oauth20": "^2.0.11",
    "@types/sharedb": "^3.2.1",
    "@types/supertest": "^2.0.11",
    "@types/uuid": "^8.3.4",
    "@types/ws": "^8.5.3",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "husky": "^8.0.1",
    "jest": "^27.2.5",
    "prettier": "^2.3.2",
    "source-map-support": "^0.5.20",
    "supertest": "^6.1.3",
    "ts-jest": "^27.0.3",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "^3.10.1",
    "typescript": "^4.3.5"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

Node.js version

18.17.1

In which operating systems have you tested?

Other

No response

jmcdo29 commented 1 year ago

Should we emit an exception event when the URL is incorrect? Something along the lines of

protected ensureHttpServerExists(
  port: number,
  httpServer = http.createServer(),
) {
  if (this.httpServersRegistry.has(port)) {
    return;
  }
  this.httpServersRegistry.set(port, httpServer);

  httpServer.on('upgrade', (request, socket, head) => {
    try {
      const baseUrl = 'ws://' + request.headers.host + '/';
      const pathname = new URL(request.url, baseUrl).pathname;
      const wsServersCollection = this.wsServersRegistry.get(port);

      let isRequestDelegated = false;
      for (const wsServer of wsServersCollection) {
        if (pathname === wsServer.path) {
          wsServer.handleUpgrade(request, socket, head, (ws: unknown) => {
            wsServer.emit('connection', ws, request);
          });
          isRequestDelegated = true;
          break;
        }
      }
      if (!isRequestDelegated) {
        socket.destroy();
      }
    } catch (err) {
      socket.write(err.message);
      socket.end();
    }
  });
  return httpServer;
}
kamilmysliwiec commented 1 year ago

We should catch that error at the very least to avoid breaking the app. Would you like to create a PR for this @dbulic-margins?

kamilmysliwiec commented 1 year ago

Let's track this here https://github.com/nestjs/nest/pull/12385