Skip to content

Commit

Permalink
Lift JSON-irrelevant functions to separate file (WICG#1328)
Browse files Browse the repository at this point in the history
* Move isLengthValid to common file

* Move suitableScope to common file
  • Loading branch information
apasel422 authored Jun 10, 2024
1 parent 8d0c722 commit 221f474
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 106 deletions.
128 changes: 22 additions & 106 deletions ts/src/header-validator/validate-json.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as psl from 'psl'
import * as uuid from 'uuid'
import * as constants from '../constants'
import { SourceType } from '../source-type'
Expand All @@ -8,10 +7,14 @@ import { Maybe } from './maybe'
import {
CtxFunc,
ItemErrorAction,
LengthOpts,
clamp,
isInteger,
isInRange,
isLengthValid,
matchesPattern,
suitableOrigin,
suitableSite,
} from './validate'
import * as validate from './validate'
import * as privacy from '../flexible-event/privacy'
Expand Down Expand Up @@ -156,34 +159,10 @@ function keyValues<V, C extends Context = Context>(
})
}

type LengthOpts = {
minLength?: number
maxLength?: number
maxLengthErrSuffix?: string
}

function list(j: Json, ctx: Context): Maybe<Json[]> {
return typeSwitch(j, ctx, { list: some })
}

function isLengthValid(
length: number,
ctx: Context,
{
minLength = 0,
maxLength = Infinity,
maxLengthErrSuffix = '',
}: LengthOpts = {}
): boolean {
if (length > maxLength || length < minLength) {
ctx.error(
`length must be in the range [${minLength}, ${maxLength}${maxLengthErrSuffix}]`
)
return false
}
return true
}

function uint64(j: Json, ctx: Context): Maybe<bigint> {
return string(j, ctx)
.filter(
Expand Down Expand Up @@ -237,79 +216,14 @@ function hex128(j: Json, ctx: Context): Maybe<bigint> {
.map(BigInt)
}

function suitableScope(
s: string,
ctx: Context,
label: string,
scope: (url: URL) => string,
rejectExtraComponents: boolean
): Maybe<string> {
let url
try {
url = new URL(s)
} catch {
ctx.error('invalid URL')
return None
}

if (
url.protocol !== 'https:' &&
!(
url.protocol === 'http:' &&
(url.hostname === 'localhost' || url.hostname === '127.0.0.1')
)
) {
ctx.error('URL must use HTTP/HTTPS and be potentially trustworthy')
return None
}

const scoped = scope(url)
if (url.toString() !== new URL(scoped).toString()) {
if (rejectExtraComponents) {
ctx.error(
`must not contain URL components other than ${label} (${scoped})`
)
return None
}
ctx.warning(
`URL components other than ${label} (${scoped}) will be ignored`
)
}
return some(scoped)
}

function suitableOrigin(
j: Json,
ctx: Context,
rejectExtraComponents: boolean = false
): Maybe<string> {
return string(j, ctx).map(
suitableScope,
ctx,
'origin',
(u) => u.origin,
rejectExtraComponents
)
}

function suitableSite(
j: Json,
ctx: Context,
rejectExtraComponents: boolean = false
): Maybe<string> {
return string(j, ctx).map(
suitableScope,
ctx,
'site',
(u) => `${u.protocol}//${psl.get(u.hostname)}`,
rejectExtraComponents
)
}

function destination(j: Json, ctx: Context): Maybe<Set<string>> {
return typeSwitch(j, ctx, {
string: (j) => suitableSite(j, ctx).map((s) => new Set([s])),
list: (j) => set(j, ctx, suitableSite, { minLength: 1, maxLength: 3 }),
list: (j) =>
set(j, ctx, (j) => string(j, ctx).map(suitableSite, ctx), {
minLength: 1,
maxLength: 3,
}),
})
}

Expand Down Expand Up @@ -1432,14 +1346,16 @@ function aggregationCoordinatorOrigin(
j: Json,
ctx: RegistrationContext
): Maybe<string> {
return suitableOrigin(j, ctx).filter((s) => {
if (!ctx.vsv.aggregationCoordinatorOrigins.includes(s)) {
const allowed = ctx.vsv.aggregationCoordinatorOrigins.join(', ')
ctx.error(`must be one of the following: ${allowed}`)
return false
}
return true
})
return string(j, ctx)
.map(suitableOrigin, ctx)
.filter((s) => {
if (!ctx.vsv.aggregationCoordinatorOrigins.includes(s)) {
const allowed = ctx.vsv.aggregationCoordinatorOrigins.join(', ')
ctx.error(`must be one of the following: ${allowed}`)
return false
}
return true
})
}

export type Trigger = CommonDebug &
Expand Down Expand Up @@ -1560,13 +1476,13 @@ export type EventLevelReport = {
}

function reportDestination(j: Json, ctx: Context): Maybe<string | string[]> {
const suitableSiteNoExtraneous = (j: Json) =>
suitableSite(j, ctx, /*rejectExtraComponents=*/ true)
const suitableSiteNoExtraneous = (s: string) =>
suitableSite(s, ctx, /*rejectExtraComponents=*/ true)

return typeSwitch<string | string[]>(j, ctx, {
string: suitableSiteNoExtraneous,
list: (j) =>
array(j, ctx, suitableSiteNoExtraneous, {
array(j, ctx, (j) => string(j, ctx).map(suitableSiteNoExtraneous), {
minLength: 2,
maxLength: 3,
}).filter((v) => {
Expand Down
88 changes: 88 additions & 0 deletions ts/src/header-validator/validate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as psl from 'psl'
import { Context, PathComponent } from './context'
import { Maybe, Maybeable } from './maybe'

Expand Down Expand Up @@ -280,3 +281,90 @@ export function clamp<N extends bigint | number>(
}
return n
}

export type LengthOpts = {
minLength?: number
maxLength?: number
maxLengthErrSuffix?: string
}

export function isLengthValid(
length: number,
ctx: Context,
{
minLength = 0,
maxLength = Infinity,
maxLengthErrSuffix = '',
}: LengthOpts = {}
): boolean {
if (length > maxLength || length < minLength) {
ctx.error(
`length must be in the range [${minLength}, ${maxLength}${maxLengthErrSuffix}]`
)
return false
}
return true
}

function suitableScope(
s: string,
ctx: Context,
label: string,
scope: (url: URL) => string,
rejectExtraComponents: boolean
): Maybe<string> {
let url
try {
url = new URL(s)
} catch {
ctx.error('invalid URL')
return Maybe.None
}

if (
url.protocol !== 'https:' &&
!(
url.protocol === 'http:' &&
(url.hostname === 'localhost' || url.hostname === '127.0.0.1')
)
) {
ctx.error('URL must use HTTP/HTTPS and be potentially trustworthy')
return Maybe.None
}

const scoped = scope(url)
if (url.toString() !== new URL(scoped).toString()) {
if (rejectExtraComponents) {
ctx.error(
`must not contain URL components other than ${label} (${scoped})`
)
return Maybe.None
}
ctx.warning(
`URL components other than ${label} (${scoped}) will be ignored`
)
}
return Maybe.some(scoped)
}

export function suitableOrigin(
s: string,
ctx: Context,
rejectExtraComponents: boolean = false
): Maybe<string> {
return suitableScope(s, ctx, 'origin', (u) => u.origin, rejectExtraComponents)
}

export function suitableSite(
s: string,
ctx: Context,
rejectExtraComponents: boolean = false
): Maybe<string> {
return suitableScope(
s,
ctx,
'site',
(u) => `${u.protocol}//${psl.get(u.hostname)}`,
rejectExtraComponents
)
}

0 comments on commit 221f474

Please sign in to comment.