{"version":3,"file":"startHttpServer.cjs","names":["http","path","fs","sandboxUserName","isContainedPath"],"sources":["../../src/helpers/startHttpServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\n\nimport { isContainedPath } from './safeFs.js';\nimport { sandboxUserName } from './sandboxUser.js';\n\nexport interface HttpServer {\n  [Symbol.asyncDispose](): Promise<void>;\n  url: string;\n  port: number | undefined;\n}\n\n/**\n * Start a HTTP server for testing web pages.\n */\nexport function startHttpServer(dir: string): HttpServer {\n  const server = http.createServer((request, response) => {\n    let pathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;\n\n    if (pathname === '/') {\n      pathname = '/index.html';\n    }\n\n    const pathnameWithIndexHtml = pathname.endsWith('/') ? path.join(pathname, 'index.html') : pathname;\n\n    const filePath = path.join(dir, pathnameWithIndexHtml);\n\n    // Under delegation this read runs as the trusted harness user while the served directory\n    // belongs to the sandboxed submission, so a planted symlink (or symlinked parent) pointing at\n    // e.g. the problem's expected outputs would otherwise be served straight back to the page.\n    if (!isServableSubmissionPath(dir, filePath)) {\n      response.statusCode = 404;\n      response.end();\n      return;\n    }\n\n    if (fs.existsSync(filePath)) {\n      try {\n        const buffer = fs.readFileSync(filePath);\n        response.writeHead(200, {\n          'Access-Control-Allow-Origin': '*',\n          Pragma: 'no-cache',\n          'Cache-Control': 'no-cache',\n          'Content-Type': getContentType(pathnameWithIndexHtml),\n        });\n        response.end(buffer);\n      } catch {\n        response.statusCode = 500;\n        response.end();\n      }\n    } else {\n      response.statusCode = 404;\n      response.end();\n    }\n  });\n\n  server.listen();\n\n  const address = server.address();\n  if (!address) throw new Error('server has been unexpectedly closed');\n\n  return {\n    [Symbol.asyncDispose]: async () => {\n      server.closeAllConnections();\n      await new Promise<void>((resolve) => {\n        server.close(() => {\n          resolve();\n        });\n      });\n    },\n    url: typeof address === 'string' ? address : `http://127.0.0.1:${address.port}`,\n    port: typeof address === 'object' ? address.port : undefined,\n  };\n}\n\nconst CONTENT_TYPE_BY_SUFFIX: Record<string, string> = {\n  '.css': 'text/css',\n  '.html': 'text/html',\n  '.js': 'text/javascript',\n  '.gif': 'image/gif',\n  '.jpg': 'image/jpeg',\n  '.png': 'image/png',\n  '.svg': 'image/svg+xml',\n};\n\nfunction isServableSubmissionPath(dir: string, filePath: string): boolean {\n  // Without user separation the page's own code can read these files anyway, so keep the previous\n  // behavior and stay usable for course authoring outside the judge image.\n  if (!sandboxUserName) return true;\n  try {\n    return isContainedPath(fs.realpathSync(dir), fs.realpathSync(filePath));\n  } catch {\n    // A missing file is reported as 404 below; anything unresolvable is not servable either.\n    return !fs.existsSync(filePath);\n  }\n}\n\nfunction getContentType(pathname: string): string {\n  for (const [suffix, contentType] of Object.entries(CONTENT_TYPE_BY_SUFFIX)) {\n    if (pathname.endsWith(suffix)) return contentType;\n  }\n  return 'text/plain';\n}\n"],"mappings":"6PAgBA,SAAgB,EAAgB,EAAyB,CACvD,IAAM,EAASA,EAAAA,QAAK,cAAc,EAAS,IAAa,CACtD,IAAI,EAAW,IAAI,IAAI,EAAQ,KAAO,IAAK,kBAAkB,CAAC,CAAC,SAE3D,IAAa,MACf,EAAW,eAGb,IAAM,EAAwB,EAAS,SAAS,GAAG,EAAIC,EAAAA,QAAK,KAAK,EAAU,YAAY,EAAI,EAErF,EAAWA,EAAAA,QAAK,KAAK,EAAK,CAAqB,EAKrD,GAAI,CAAC,EAAyB,EAAK,CAAQ,EAAG,CAC5C,EAAS,WAAa,IACtB,EAAS,IAAI,EACb,MACF,CAEA,GAAIC,EAAAA,QAAG,WAAW,CAAQ,EACxB,GAAI,CACF,IAAM,EAASA,EAAAA,QAAG,aAAa,CAAQ,EACvC,EAAS,UAAU,IAAK,CACtB,8BAA+B,IAC/B,OAAQ,WACR,gBAAiB,WACjB,eAAgB,EAAe,CAAqB,CACtD,CAAC,EACD,EAAS,IAAI,CAAM,CACrB,MAAQ,CACN,EAAS,WAAa,IACtB,EAAS,IAAI,CACf,KAEA,GAAS,WAAa,IACtB,EAAS,IAAI,CAEjB,CAAC,EAED,EAAO,OAAO,EAEd,IAAM,EAAU,EAAO,QAAQ,EAC/B,GAAI,CAAC,EAAS,MAAU,MAAM,qCAAqC,EAEnE,MAAO,EACJ,OAAO,cAAe,SAAY,CACjC,EAAO,oBAAoB,EAC3B,MAAM,IAAI,QAAe,GAAY,CACnC,EAAO,UAAY,CACjB,EAAQ,CACV,CAAC,CACH,CAAC,CACH,EACA,IAAK,OAAO,GAAY,SAAW,EAAU,oBAAoB,EAAQ,OACzE,KAAM,OAAO,GAAY,SAAW,EAAQ,KAAO,IAAA,EACrD,CACF,CAEA,MAAM,EAAiD,CACrD,OAAQ,WACR,QAAS,YACT,MAAO,kBACP,OAAQ,YACR,OAAQ,aACR,OAAQ,YACR,OAAQ,eACV,EAEA,SAAS,EAAyB,EAAa,EAA2B,CAGxE,GAAI,CAACC,EAAAA,gBAAiB,MAAO,GAC7B,GAAI,CACF,OAAOC,EAAAA,gBAAgBF,EAAAA,QAAG,aAAa,CAAG,EAAGA,EAAAA,QAAG,aAAa,CAAQ,CAAC,CACxE,MAAQ,CAEN,MAAO,CAACA,EAAAA,QAAG,WAAW,CAAQ,CAChC,CACF,CAEA,SAAS,EAAe,EAA0B,CAChD,IAAK,GAAM,CAAC,EAAQ,KAAgB,OAAO,QAAQ,CAAsB,EACvE,GAAI,EAAS,SAAS,CAAM,EAAG,OAAO,EAExC,MAAO,YACT"}