참고소스 수정본

This commit is contained in:
LASTA_DEV01\lasta
2026-05-12 19:40:31 +09:00
parent 0f34a451fc
commit 2e9204243d
8708 changed files with 3259488 additions and 869 deletions

View File

@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFCjCCAvKgAwIBAgIULU/gkDm8IqC7PG8u3RID0AYyP6gwDQYJKoZIhvcNAQEL
BQAwGjEYMBYGA1UEAwwPcGxheXdyaWdodC10ZXN0MB4XDTIzMDgxMDIyNTc1MFoX
DTMzMDgwNzIyNTc1MFowGjEYMBYGA1UEAwwPcGxheXdyaWdodC10ZXN0MIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArbS99qjKcnHr5G0Zc2xhDaOZnjQv
Fbiqxf/nbXt/7WaqryzpVKu7AT1ainBvuPEo7If9DhVnfF//2pGl0gbU31OU4/mr
ymQmczGEyZvOBDsZhtCif54o5OoO0BjhODNT8OWec9RT87n6RkH58MHlOi8xsPxQ
9n5U1CN/h2DyQF3aRKunEFCgtwPKWSjG+J/TAI9i0aSENXPiR8wjTrjg79s8Ehuj
NN8Wk6rKLU3sepG3GIMID5vLsVa2t9xqn562sP95Ee+Xp2YX3z7oYK99QCJdzacw
alhMHob1GCEKjDyxsD2IFRi7Dysiutfyzy3pMo6NALxFrwKVhWX0L4zVFIsI6JlV
dK8dHmDk0MRSqgB9sWXvEfSTXADEe8rncFSFpFz4Z8RNLmn5YSzQJzokNn41DUCP
dZTlTkcGTqvn5NqoY4sOV8rkFbgmTcqyijV/sebPjxCbJNcNmaSWa9FJ5IjRTpzM
38wLmxn+eKGK68n2JB3P7JP6LtsBShQEpXAF3rFfyNsP1bjquvGZVSjV8w/UwPE4
kV5eq3j3D4913Zfxvzjp6PEmhStG0EQtIXvx/TRoYpaNWypIgZdbkZQp1HUIQL15
D2Web4nazP3so1FC3ZgbrJZ2ozoadjLMp49NcSFdh+WRyVKuo0DIqR0zaiAzzf2D
G1q7TLKimM3XBMUCAwEAAaNIMEYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwLAYD
VR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqG
SIb3DQEBCwUAA4ICAQAvC5M1JFc21WVSLPvE2iVbt4HmirO3EENdDqs+rTYG5VJG
iE5ZuI6h/LjS5ptTfKovXQKaMr3pwp1pLMd/9q+6ZR1Hs9Z2wF6OZan4sb0uT32Y
1KGlj86QMiiSLdrJ/1Z9JHskHYNCep1ZTsUhGk0qqiNv+G3K2y7ZpvrT/xlnYMth
KLTuSVUwM8BBEPrCRLoXuaEy0LnvMvMVepIfP8tnMIL6zqmj3hXMPe4r4OFV/C5o
XX25bC7GyuPWIRYn2OWP92J1CODZD1rGRoDtmvqrQpHdeX9RYcKH0ZLZoIf5L3Hf
pPUtVkw3QGtjvKeG3b9usxaV9Od2Z08vKKk1PRkXFe8gqaeyicK7YVIOMTSuspAf
JeJEHns6Hg61Exbo7GwdX76xlmQ/Z43E9BPHKgLyZ9WuJ0cysqN4aCyvS9yws9to
ki7iMZqJUsmE2o09n9VaEsX6uQANZtLjI9wf+IgJuueDTNrkzQkhU7pbaPMsSG40
AgGY/y4BR0H8sbhNnhqtZH7RcXV9VCJoPBAe+YiuXRiXyZHWxwBRyBE3e7g4MKHg
hrWtaWUAs7gbavHwjqgU63iVItDSk7t4fCiEyObjK09AaNf2DjjaSGf8YGza4bNy
BjYinYJ6/eX//gp+abqfocFbBP7D9zRDgMIbVmX/Ey6TghKiLkZOdbzcpO4Wgg==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,350 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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 fs from 'fs';
import type http from 'http';
import mime from 'mime';
import type net from 'net';
import path from 'path';
import util from 'util';
import type stream from 'stream';
import { WebSocket, WebSocketServer } from 'ws';
import zlib, { gzip } from 'zlib';
import { utils } from '../../../packages/playwright-core/lib/coreBundle';
const { createHttpServer, createHttpsServer } = utils;
const fulfillSymbol = Symbol('fulfil callback');
const rejectSymbol = Symbol('reject callback');
const gzipAsync = util.promisify(gzip.bind(zlib));
type UpgradeActions = {
doUpgrade: () => void;
socket: stream.Duplex;
};
type IncomingMessageWithBody = http.IncomingMessage & { postBody: Promise<Buffer> };
export class TestServer {
private _server: http.Server;
private _wsServer: WebSocketServer;
private _dirPath: string;
readonly debugServer: any;
private _startTime: Date;
private _cachedPathPrefix: string | null;
private _routes = new Map<string, (arg0: IncomingMessageWithBody, arg1: http.ServerResponse) => any>();
private _auths = new Map<string, { username: string; password: string; }>();
private _csp = new Map<string, string>();
private _extraHeaders = new Map<string, object>();
private _gzipRoutes = new Set<string>();
private _requestSubscribers = new Map<string, Promise<any>>();
private _upgradeCallback: (actions: UpgradeActions) => void | undefined;
readonly PORT: number;
readonly PREFIX: string;
readonly CROSS_PROCESS_PREFIX: string;
readonly EMPTY_PAGE: string;
readonly HOST: string;
readonly HOSTNAME: string;
readonly HELLO_WORLD: string;
static async create(dirPath: string, port: number, loopback?: string): Promise<TestServer> {
const server = new TestServer(dirPath, port, loopback);
await server.waitUntilReady();
return server;
}
static async certOptions() {
return {
key: await fs.promises.readFile(path.join(__dirname, 'key.pem')),
cert: await fs.promises.readFile(path.join(__dirname, 'cert.pem')),
passphrase: 'aaaa',
};
}
static async createHTTPS(dirPath: string, port: number, loopback?: string): Promise<TestServer> {
const server = new TestServer(dirPath, port, loopback, await this.certOptions());
await server.waitUntilReady();
return server;
}
constructor(dirPath: string, port: number, loopback?: string, sslOptions?: object) {
if (sslOptions)
this._server = createHttpsServer(sslOptions, this._onRequest.bind(this));
else
this._server = createHttpServer(this._onRequest.bind(this));
this._server.on('connection', socket => this._onSocket(socket));
this._wsServer = new WebSocketServer({ noServer: true });
this._server.on('upgrade', async (request, socket, head) => {
const doUpgrade = () => {
this._wsServer.handleUpgrade(request, socket, head, ws => {
// Next emit is only for our internal 'connection' listeners.
this._wsServer.emit('connection', ws, request);
});
};
if (this._upgradeCallback) {
this._upgradeCallback({ doUpgrade, socket });
return;
}
const pathname = new URL(request.url, 'http://localhost').pathname;
if (pathname === '/ws-401') {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\nUnauthorized body');
socket.destroy();
return;
}
if (pathname === '/ws-slow')
await new Promise(f => setTimeout(f, 2000));
if (!['/ws', '/ws-slow'].includes(pathname)) {
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
socket.destroy();
return;
}
doUpgrade();
});
this._server.listen(port);
this._dirPath = dirPath;
this.debugServer = require('debug')('pw:testserver');
this._startTime = new Date();
this._cachedPathPrefix = null;
const cross_origin = loopback || '127.0.0.1';
const same_origin = loopback || 'localhost';
const protocol = sslOptions ? 'https' : 'http';
this.PORT = port;
this.PREFIX = `${protocol}://${same_origin}:${port}`;
this.CROSS_PROCESS_PREFIX = `${protocol}://${cross_origin}:${port}`;
this.EMPTY_PAGE = `${protocol}://${same_origin}:${port}/empty.html`;
this.HOST = new URL(this.EMPTY_PAGE).host;
this.HOSTNAME = new URL(this.EMPTY_PAGE).hostname;
this.HELLO_WORLD = `${this.PREFIX}/hello-world`;
}
async waitUntilReady() {
await new Promise(x => this._server.once('listening', x));
}
_onSocket(socket: net.Socket) {
// ECONNRESET and HPE_INVALID_EOF_STATE are legit errors given
// that tab closing aborts outgoing connections to the server.
// HPE_INVALID_METHOD is a legit error when a client (e.g. Chromium which
// makes https requests to http sites) makes a https connection to a http server.
socket.on('error', error => {
if (!['ECONNRESET', 'HPE_INVALID_EOF_STATE', 'HPE_INVALID_METHOD'].includes((error as any).code))
throw error;
});
}
enableHTTPCache(pathPrefix: string) {
this._cachedPathPrefix = pathPrefix;
}
setAuth(path: string, username: string, password: string) {
this.debugServer(`set auth for ${path} to ${username}:${password}`);
this._auths.set(path, { username, password });
}
enableGzip(path: string) {
this._gzipRoutes.add(path);
}
setCSP(path: string, csp: string) {
this._csp.set(path, csp);
}
setExtraHeaders(path: string, object: Record<string, string>) {
this._extraHeaders.set(path, object);
}
async stop() {
this.reset();
await new Promise(x => this._server.close(x));
}
setContent(path: string, content: string, mimeType: string) {
this.setRoute(path, (req, res) => {
res.writeHead(200, { 'Content-Type': mimeType });
res.end(mimeType === 'text/html' ? `<!DOCTYPE html>${content}` : content);
});
}
setRoute(path: string, handler: (arg0: IncomingMessageWithBody, arg1: http.ServerResponse) => any) {
this._routes.set(path, handler);
}
setRedirect(from: string, to: string) {
this.setRoute(from, (req, res) => {
const headers = this._extraHeaders.get(req.url!) || {};
res.writeHead(302, { ...headers, location: to });
res.end();
});
}
waitForRequest(path: string): Promise<IncomingMessageWithBody> {
let promise = this._requestSubscribers.get(path);
if (promise)
return promise;
let fulfill;
let reject;
promise = new Promise((f, r) => {
fulfill = f;
reject = r;
});
promise[fulfillSymbol] = fulfill;
const error = new Error(`Request ${path} was not received before the test finished.`);
promise[rejectSymbol] = () => reject(error);
this._requestSubscribers.set(path, promise);
return promise;
}
reset() {
this._routes.clear();
this._auths.clear();
this._csp.clear();
this._extraHeaders.clear();
this._gzipRoutes.clear();
this._upgradeCallback = undefined;
this._wsServer.removeAllListeners('connection');
this._server.closeAllConnections();
for (const subscriber of this._requestSubscribers.values())
subscriber[rejectSymbol].call(null);
this._requestSubscribers.clear();
}
_onRequest(request: http.IncomingMessage, response: http.ServerResponse) {
request.on('error', error => {
if ((error as any).code === 'ECONNRESET')
response.end();
else
throw error;
});
(request as any).postBody = new Promise(resolve => {
const chunks: Buffer[] = [];
request.on('data', chunk => {
chunks.push(chunk);
});
request.on('end', () => resolve(Buffer.concat(chunks)));
});
const url = new URL(request.url, 'http://localhost');
const pathWithSearch = url.pathname + url.search;
this.debugServer(`request ${request.method} ${pathWithSearch}`);
if (this._auths.has(pathWithSearch)) {
const auth = this._auths.get(pathWithSearch)!;
const credentials = Buffer.from((request.headers.authorization || '').split(' ')[1] || '', 'base64').toString();
this.debugServer(`request credentials ${credentials}`);
this.debugServer(`actual credentials ${auth.username}:${auth.password}`);
if (credentials !== `${auth.username}:${auth.password}`) {
this.debugServer(`request write www-auth`);
response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Secure Area"' });
response.end('HTTP Error 401 Unauthorized: Access is denied');
return;
}
}
// Notify request subscriber.
if (this._requestSubscribers.has(pathWithSearch)) {
this._requestSubscribers.get(pathWithSearch)![fulfillSymbol].call(null, request);
this._requestSubscribers.delete(pathWithSearch);
}
const handler = this._routes.get(pathWithSearch);
if (handler)
handler.call(null, request as IncomingMessageWithBody, response);
else
this.serveFile(request, response);
}
serveFile(request: http.IncomingMessage, response: http.ServerResponse, filePath?: string): void {
this._serveFile(request, response, filePath).catch(e => {
this.debugServer(`error: ${e}`);
});
}
private async _serveFile(request: http.IncomingMessage, response: http.ServerResponse, filePath?: string): Promise<void> {
let pathName = new URL(request.url, 'http://localhost').pathname;
if (!filePath) {
if (pathName === '/')
pathName = '/index.html';
filePath = path.join(this._dirPath, pathName.substring(1));
}
if (this._cachedPathPrefix !== null && filePath.startsWith(this._cachedPathPrefix)) {
if (request.headers['if-modified-since']) {
response.statusCode = 304; // not modified
response.end();
return;
}
response.setHeader('Cache-Control', 'public, max-age=31536000, no-cache');
response.setHeader('Last-Modified', this._startTime.toISOString());
} else {
response.setHeader('Cache-Control', 'no-cache, no-store');
}
if (this._csp.has(pathName))
response.setHeader('Content-Security-Policy', this._csp.get(pathName)!);
if (this._extraHeaders.has(pathName)) {
const object = this._extraHeaders.get(pathName);
for (const key in object)
response.setHeader(key, object[key]);
}
const { err, data } = await fs.promises.readFile(filePath).then(data => ({ data, err: undefined })).catch(err => ({ data: undefined, err }));
// The HTTP transaction might be already terminated after async hop here - do nothing in this case.
if (response.writableEnded)
return;
if (err) {
response.statusCode = 404;
response.setHeader('Content-Type', 'text/plain');
response.end(request.method !== 'HEAD' ? `File not found: ${filePath}` : null);
return;
}
const extension = filePath.substring(filePath.lastIndexOf('.') + 1);
const mimeType = mime.getType(extension) || 'application/octet-stream';
const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(mimeType);
const contentType = isTextEncoding ? `${mimeType}; charset=utf-8` : mimeType;
response.setHeader('Content-Type', contentType);
if (this._gzipRoutes.has(pathName)) {
response.setHeader('Content-Encoding', 'gzip');
const result = await gzipAsync(data);
// The HTTP transaction might be already terminated after async hop here.
if (!response.writableEnded)
response.end(request.method !== 'HEAD' ? result : null);
} else {
response.end(request.method !== 'HEAD' ? data : null);
}
}
onceWebSocketConnection(handler: (socket: WebSocket, request: http.IncomingMessage) => void) {
this._wsServer.once('connection', handler);
}
waitForWebSocketConnectionRequest() {
return new Promise<http.IncomingMessage & { headers: http.IncomingHttpHeaders }>(fulfil => {
this._wsServer.once('connection', (ws, req) => fulfil(req));
});
}
waitForUpgrade() {
return new Promise<UpgradeActions>(fulfill => this._upgradeCallback = fulfill);
}
waitForWebSocket() {
return new Promise<WebSocket>(fulfill => this._wsServer.once('connection', (ws, req) => fulfill(ws)));
}
sendOnWebSocketConnection(data) {
this.onceWebSocketConnection(ws => ws.send(data));
}
}

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
ZkZVSOEp+sYBf/tmptlKr49nO+dTjQ==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,19 @@
# openssl req -new -x509 -days 3650 -key key.pem -out cert.pem -config san.cnf -extensions v3_req
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = playwright-test
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = ::1