diff --git a/packages/utils/fileUtils.ts b/packages/utils/fileUtils.ts index 4328e5640db4f..7b87e20193c67 100644 --- a/packages/utils/fileUtils.ts +++ b/packages/utils/fileUtils.ts @@ -113,7 +113,13 @@ export function makeSocketPath(domain: string, name: string): string { } const baseDir = process.env.PLAYWRIGHT_SOCKETS_DIR || path.join(os.tmpdir(), `pw-${userNameHash}`); const dir = path.join(baseDir, domain); - const result = path.join(dir, `${name}.sock`); fs.mkdirSync(dir, { recursive: true }); + let result = path.join(dir, `${name}.sock`); + // sockaddr_un.sun_path caps unix socket paths at 104 bytes on macOS/BSD and + // 108 on Linux. A longer path fails with EINVAL or binds to a truncated + // path, so fall back to a hashed name to stay within the limit. + const maxSocketPathLength = process.platform === 'linux' ? 107 : 103; + if (Buffer.byteLength(result) > maxSocketPathLength) + result = path.join(dir, `${calculateSha1(name).slice(0, 16)}.sock`); return result; } diff --git a/tests/library/unit/socket-path.spec.ts b/tests/library/unit/socket-path.spec.ts new file mode 100644 index 0000000000000..907a2301db018 --- /dev/null +++ b/tests/library/unit/socket-path.spec.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test as it, expect } from '@playwright/test'; +import fs from 'fs'; +import net from 'net'; +import { makeSocketPath } from '../../../packages/utils/fileUtils'; + +it.describe('makeSocketPath', () => { + it.skip(process.platform === 'win32', 'Windows named pipes are not constrained by sockaddr_un.sun_path'); + + it('should keep a long socket name within the OS path length limit', () => { + const socketPath = makeSocketPath('cli', 'session-' + 'a'.repeat(200)); + // sockaddr_un.sun_path holds 104 bytes on macOS/BSD and 108 on Linux. + const limit = process.platform === 'linux' ? 107 : 103; + expect(Buffer.byteLength(socketPath)).toBeLessThanOrEqual(limit); + expect(socketPath.endsWith('.sock')).toBe(true); + }); + + it('should produce a listenable socket path for a long name', async () => { + const socketPath = makeSocketPath('cli', 'listen-' + 'b'.repeat(200)); + const server = net.createServer(); + try { + await new Promise((resolve, reject) => { + server.on('error', reject); + server.listen(socketPath, resolve); + }); + // The socket must exist at exactly the returned path; a path over the + // limit would silently bind to a truncated one and break clients. + expect(fs.existsSync(socketPath)).toBe(true); + } finally { + await new Promise(resolve => server.close(() => resolve())); + fs.rmSync(socketPath, { force: true }); + } + }); + + it('should keep short socket names human-readable', () => { + expect(makeSocketPath('cli', 'default').endsWith('default.sock')).toBe(true); + }); +});