From 219e5138be3e373bb651bf869895ae2d788f0f1e Mon Sep 17 00:00:00 2001 From: Ross Wollman Date: Thu, 17 Jun 2021 13:04:55 -0700 Subject: [PATCH] feat(response): expose connection details in API (#7150) --- docs/src/api/class-response.md | 21 +++++++++++++ src/client/network.ts | 10 +++++- src/client/types.ts | 3 ++ src/dispatchers/networkDispatchers.ts | 8 +++++ src/protocol/channels.ts | 25 +++++++++++++++ src/protocol/protocol.yml | 25 +++++++++++++++ src/protocol/validator.ts | 13 ++++++++ tests/har.spec.ts | 22 ++++++++++++++ types/types.d.ts | 44 +++++++++++++++++++++++++++ 9 files changed, 170 insertions(+), 1 deletion(-) diff --git a/docs/src/api/class-response.md b/docs/src/api/class-response.md index 385ca95536..570ee2b06f 100644 --- a/docs/src/api/class-response.md +++ b/docs/src/api/class-response.md @@ -48,6 +48,27 @@ Contains a boolean stating whether the response was successful (status in the ra Returns the matching [Request] object. +## async method: Response.securityDetails +- returns: <[null]|[Object]> + - `issuer` <[string]> Common Name component of the Issuer field. + from the certificate. This should only be used for informational purposes. Optional. + - `protocol` <[string]> The specific TLS protocol used. (e.g. `TLS 1.3`). Optional. + - `subjectName` <[string]> Common Name component of the Subject + field from the certificate. This should only be used for informational purposes. Optional. + - `validFrom` <[int]> Unix timestamp (in seconds) specifying + when this cert becomes valid. Optional. + - `validTo` <[int]> Unix timestamp (in seconds) specifying + when this cert becomes invalid. Optional. + +Returns SSL and other security information. + +## async method: Response.serverAddr +- returns: <[null]|[Object]> + - `ipAddress` <[string]> IPv4 or IPV6 address of the server. + - `port` <[int]> + +Returns the IP address and port of the server. + ## method: Response.status - returns: <[int]> diff --git a/src/client/network.ts b/src/client/network.ts index 2dad65d4bb..e385c22d6c 100644 --- a/src/client/network.ts +++ b/src/client/network.ts @@ -18,7 +18,7 @@ import { URLSearchParams } from 'url'; import * as channels from '../protocol/channels'; import { ChannelOwner } from './channelOwner'; import { Frame } from './frame'; -import { Headers, WaitForEventOptions } from './types'; +import { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types'; import fs from 'fs'; import * as mime from 'mime'; import { isString, headersObjectToArray, headersArrayToObject } from '../utils/utils'; @@ -325,6 +325,14 @@ export class Response extends ChannelOwner { + return (await this._channel.serverAddr()).value || null; + } + + async securityDetails(): Promise { + return (await this._channel.securityDetails()).value || null; + } } export class WebSocket extends ChannelOwner implements api.WebSocket { diff --git a/src/client/types.ts b/src/client/types.ts index cae78ea5a3..9bcdf99af5 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -111,3 +111,6 @@ export type SelectorEngine = { */ queryAll(root: HTMLElement, selector: string): HTMLElement[]; }; + +export type RemoteAddr = channels.RemoteAddr; +export type SecurityDetails = channels.SecurityDetails; diff --git a/src/dispatchers/networkDispatchers.ts b/src/dispatchers/networkDispatchers.ts index 4441ba7aad..5bd337aeb1 100644 --- a/src/dispatchers/networkDispatchers.ts +++ b/src/dispatchers/networkDispatchers.ts @@ -80,6 +80,14 @@ export class ResponseDispatcher extends Dispatcher { return { binary: (await this._object.body()).toString('base64') }; } + + async securityDetails(): Promise { + return { value: await this._object.securityDetails() || undefined }; + } + + async serverAddr(): Promise { + return { value: await this._object.serverAddr() || undefined }; + } } export class RouteDispatcher extends Dispatcher implements channels.RouteChannel { diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index 31cba1d2b9..453ad17981 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -2372,6 +2372,8 @@ export type ResponseInitializer = { export interface ResponseChannel extends Channel { body(params?: ResponseBodyParams, metadata?: Metadata): Promise; finished(params?: ResponseFinishedParams, metadata?: Metadata): Promise; + securityDetails(params?: ResponseSecurityDetailsParams, metadata?: Metadata): Promise; + serverAddr(params?: ResponseServerAddrParams, metadata?: Metadata): Promise; } export type ResponseBodyParams = {}; export type ResponseBodyOptions = {}; @@ -2383,6 +2385,29 @@ export type ResponseFinishedOptions = {}; export type ResponseFinishedResult = { error?: string, }; +export type ResponseSecurityDetailsParams = {}; +export type ResponseSecurityDetailsOptions = {}; +export type ResponseSecurityDetailsResult = { + value?: SecurityDetails, +}; +export type ResponseServerAddrParams = {}; +export type ResponseServerAddrOptions = {}; +export type ResponseServerAddrResult = { + value?: RemoteAddr, +}; + +export type SecurityDetails = { + issuer?: string, + protocol?: string, + subjectName?: string, + validFrom?: number, + validTo?: number, +}; + +export type RemoteAddr = { + ipAddress: string, + port: number, +}; // ----------- WebSocket ----------- export type WebSocketInitializer = { diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index 71b61827f1..5a7fdd3a28 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -1927,6 +1927,31 @@ Response: returns: error: string? + securityDetails: + returns: + value: SecurityDetails? + + serverAddr: + returns: + value: RemoteAddr? + + +SecurityDetails: + type: object + properties: + issuer: string? + protocol: string? + subjectName: string? + validFrom: number? + validTo: number? + + +RemoteAddr: + type: object + properties: + ipAddress: string + port: number + WebSocket: type: interface diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index 3ba41f2852..024ea5013f 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -943,6 +943,19 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { }); scheme.ResponseBodyParams = tOptional(tObject({})); scheme.ResponseFinishedParams = tOptional(tObject({})); + scheme.ResponseSecurityDetailsParams = tOptional(tObject({})); + scheme.ResponseServerAddrParams = tOptional(tObject({})); + scheme.SecurityDetails = tObject({ + issuer: tOptional(tString), + protocol: tOptional(tString), + subjectName: tOptional(tString), + validFrom: tOptional(tNumber), + validTo: tOptional(tNumber), + }); + scheme.RemoteAddr = tObject({ + ipAddress: tString, + port: tNumber, + }); scheme.BindingCallRejectParams = tObject({ error: tType('SerializedError'), }); diff --git a/tests/har.spec.ts b/tests/har.spec.ts index 4c9c66cf1e..760fb9bcc4 100644 --- a/tests/har.spec.ts +++ b/tests/har.spec.ts @@ -363,3 +363,25 @@ it('should have connection details for failed requests', async ({ contextFactory expect(serverIPAddress).toMatch(/^127\.0\.0\.1|\[::1\]/); expect(port).toBe(server.PORT); }); + +it('should return server address directly from response', async ({ page, server }) => { + const response = await page.goto(server.EMPTY_PAGE); + const { ipAddress, port } = await response.serverAddr(); + expect(ipAddress).toMatch(/^127\.0\.0\.1|\[::1\]/); + expect(port).toBe(server.PORT); +}); + +it('should return security details directly from response', async ({ contextFactory, httpsServer, browserName, platform }) => { + it.fail(browserName === 'webkit' && platform === 'linux', 'https://github.com/microsoft/playwright/issues/6759'); + + const context = await contextFactory({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + const response = await page.goto(httpsServer.EMPTY_PAGE); + const securityDetails = await response.securityDetails(); + if (browserName === 'webkit' && platform === 'win32') + expect(securityDetails).toEqual({subjectName: 'puppeteer-tests', validFrom: 1550084863, validTo: -1}); + else if (browserName === 'webkit') + expect(securityDetails).toEqual({protocol: 'TLS 1.3', subjectName: 'puppeteer-tests', validFrom: 1550084863, validTo: 33086084863}); + else + expect(securityDetails).toEqual({issuer: 'puppeteer-tests', protocol: 'TLS 1.3', subjectName: 'puppeteer-tests', validFrom: 1550084863, validTo: 33086084863}); +}); diff --git a/types/types.d.ts b/types/types.d.ts index eb7aa6c23c..ec994ca77e 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -10363,6 +10363,50 @@ export interface Response { */ request(): Request; + /** + * Returns SSL and other security information. + */ + securityDetails(): Promise; + + /** + * Returns the IP address and port of the server. + */ + serverAddr(): Promise; + /** * Contains the status code of the response (e.g., 200 for a success). */