Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function validateTraceUrlOrPath(traceFileOrUrl: string | undefined): string | un
}

export async function startTraceViewerServer(options?: TraceViewerServerOptions): Promise<HttpServer> {
const server = new HttpServer();
const server = new HttpServer(libPath('vite', 'traceViewer'));
const allowedRoots = (options?.allowedFileRoots ?? [process.cwd()]).map(r => path.resolve(r));
const isAllowed = (filePath: string) => allowedRoots.some(root => isPathInside(root, filePath));

Expand All @@ -105,7 +105,7 @@ export async function startTraceViewerServer(options?: TraceViewerServerOptions)
return true;
}
if (fs.existsSync(filePath))
return server.serveFile(request, response, filePath);
return server.serveFile(request, response, filePath, undefined, { skipRootCheck: true });

// If .json is requested, we'll synthesize it for zip-less operation.
if (filePath.endsWith('.json')) {
Expand Down
4 changes: 1 addition & 3 deletions packages/playwright-core/src/tools/dashboard/dashboardApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ type DashboardServer = {
};

async function startDashboardServer(provider: SessionProvider, options: DashboardOptions): Promise<DashboardServer> {
const httpServer = new HttpServer();
const dashboardDir = libPath('vite', 'dashboard');
const httpServer = new HttpServer(dashboardDir);

const connections = new Set<DashboardConnection>();
let currentReveal: DashboardOptions = options;
Expand Down Expand Up @@ -152,8 +152,6 @@ function attachDashboardStaticServer(httpServer: HttpServer, dashboardDir: strin
const pathname = new URL(request.url!, `http://${request.headers.host}`).pathname;
const filePath = pathname === '/' ? 'index.html' : pathname.substring(1);
const resolved = path.join(dashboardDir, filePath);
if (!resolved.startsWith(dashboardDir))
return false;
return httpServer.serveFile(request, response, resolved);
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright/src/reporters/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export async function showHTMLReport(reportFolder: string | undefined, host: str
// the generated index.html; we extract it and splice it into Vite's
// transformed HTML so the client still finds it at runtime.
async function serveHtmlReportWithHMR(folder: string): Promise<HttpServer> {
const server = new HttpServer();
const server = new HttpServer(folder);
const reporterRoot = path.resolve(__dirname, '..', '..', '..', 'html-reporter');
const devServer = await server.createViteDevServer({ root: reporterRoot });
const generatedIndex = await fs.promises.readFile(path.join(folder, 'index.html'), 'utf-8');
Expand All @@ -314,7 +314,7 @@ async function serveHtmlReportWithHMR(folder: string): Promise<HttpServer> {
// Serve attachments and the bundled trace-viewer copy from the generated
// output folder first, falling through to Vite for source modules.
const absolutePath = path.join(folder, ...url.pathname.split('/'));
if (absolutePath.startsWith(folder) && server.serveFile(request, response, absolutePath))
if (server.serveFile(request, response, absolutePath))
return true;
devServer.middlewares(request, response, HttpServer.notFoundFallback(response));
return true;
Expand Down
19 changes: 10 additions & 9 deletions packages/utils/httpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ export class HttpServer {
private _wsGuid: string | undefined;
// Allowed Host headers; null disables the check (host bound to a public address).
private _allowedHosts: Set<string> | null = null;
private _staticRoot: string | undefined;

constructor() {
constructor(staticRoot?: string) {
this._server = createHttpServer(this._onRequest.bind(this));
this._staticRoot = staticRoot ? path.resolve(staticRoot) : undefined;
}

server() {
Expand Down Expand Up @@ -183,7 +185,12 @@ export class HttpServer {
return purpose === 'human-readable' ? this._urlPrefixHumanReadable : this._urlPrefixPrecise;
}

serveFile(request: http.IncomingMessage, response: http.ServerResponse, absoluteFilePath: string, headers?: { [name: string]: string }): boolean {
serveFile(request: http.IncomingMessage, response: http.ServerResponse, absoluteFilePath: string, headers?: { [name: string]: string }, options?: { skipRootCheck?: boolean }): boolean {
if (this._staticRoot && !options?.skipRootCheck && !isPathInside(this._staticRoot, absoluteFilePath)) {
response.statusCode = 403;
response.end();
return true;
}
try {
for (const [name, value] of Object.entries(headers || {}))
response.setHeader(name, value);
Expand Down Expand Up @@ -317,8 +324,7 @@ export function hostnameFromHostHeader(host: string): string {
}

export function serveFolder(folder: string): HttpServer {
const server = new HttpServer();
const folderRoot = path.resolve(folder);
const server = new HttpServer(folder);
server.routePrefix('/', (request, response) => {
let relativePath = new URL('http://localhost' + request.url).pathname;
if (relativePath.startsWith('/trace/file')) {
Expand All @@ -327,11 +333,6 @@ export function serveFolder(folder: string): HttpServer {
if (!requested)
return false;
const resolved = path.resolve(requested);
if (!isPathInside(folderRoot, resolved)) {
response.statusCode = 403;
response.end();
return true;
}
try {
return server.serveFile(request, response, resolved);
} catch (e) {
Expand Down
Loading