Skip to content
Open
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
15 changes: 8 additions & 7 deletions api.bs
Original file line number Diff line number Diff line change
Expand Up @@ -1922,17 +1922,17 @@ the {{AttributionImpressionOptions}} dictionary passed to
<dt><dfn noexport><code>histogram-index</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionImpressionOptions>histogramIndex</a>,
a non-negative [=structured header/integer=]. This key is required.
an [=structured header/integer=] in the [=32-bit unsigned integer=] range. This key is required.
</dd>
<dt><dfn noexport><code>priority</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionImpressionOptions>priority</a>,
an [=structured header/integer=]. This key is optional.
an [=structured header/integer=] in the [=32-bit signed integer=] range. This key is optional.
</dd>
<dt><dfn noexport><code>match-value</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionImpressionOptions>matchValue</a>,
a non-negative [=structured header/integer=]. This key is optional.
an [=structured header/integer=] in the [=32-bit unsigned integer=] range. This key is optional.
If not supplied, a value of 0 is saved for [=impression/Match Value=].
</dd>
<dt><dfn noexport><code>lifetime-days</code></dfn></dt>
Expand All @@ -1952,7 +1952,7 @@ To <dfn noexport>parse a `Save-Impression` header</dfn> given a [=header value=]
<var ignore>field_type</var> set to "`dictionary`".
1. If parsing failed, return an error.
1. If |dict|["<code>[=save-impression/histogram-index=]</code>"] does not [=map/exist=] or
is not an [=structured header/integer=] or is less than 0, return an error.
is not an [=structured header/integer=] in the [=32-bit unsigned integer=] range, return an error.
1. Let |histogramIndex| be |dict|["<code>[=save-impression/histogram-index=]</code>"].
1. Let |conversionSites| be |dict|["<code>[=save-impression/conversion-sites=]</code>"]
[=map/with default=] an empty [=structured header/inner list=].
Expand All @@ -1961,11 +1961,12 @@ To <dfn noexport>parse a `Save-Impression` header</dfn> given a [=header value=]
[=map/with default=] an empty [=structured header/inner list=].
1. If |conversionCallers| is not an [=structured header/inner list=], or if any of |conversionCallers|' [=list/items=] is not a [=structured header/string=], return an error.
1. Let |matchValue| be |dict|["<code>[=save-impression/match-value=]</code>"] [=map/with default=] 0.
1. If |matchValue| is not an [=structured header/integer=] or is less than 0, return an error.
1. If |matchValue| is not an [=structured header/integer=] in the [=32-bit unsigned integer=] range, return an error.
1. Let |lifetimeDays| be |dict|["<code>[=save-impression/lifetime-days=]</code>"] [=map/with default=] 30.
1. If |lifetimeDays| is not an [=structured header/integer=] or is less than or equal to 0, return an error.
1. If |lifetimeDays| is not a positive [=structured header/integer=], return an error.
1. Clamp |lifetimeDays| to the [=32-bit unsigned integer=] range.
1. Let |priority| be |dict|["<code>[=save-impression/priority=]</code>"] [=map/with default=] 0.
1. If |priority| is not an [=structured header/integer=], return an error.
1. If |priority| is not an [=structured header/integer=] in the [=32-bit signed integer=] range, return an error.
1. Return a new {{AttributionImpressionOptions}} with the following items:
: {{AttributionImpressionOptions/histogramIndex}}
:: |histogramIndex|
Expand Down
77 changes: 77 additions & 0 deletions impl/src/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ runTests([
{ name: "histogram-index-negative", input: "histogram-index=-1" },
{ name: "histogram-index-not-integer", input: "histogram-index=1.2" },

{
name: "valid-histogram-index-eq-32-bit-max",
input: "histogram-index=4294967295",
expected: {
histogramIndex: 4294967295,
matchValue: 0,
conversionSites: [],
conversionCallers: [],
lifetimeDays: 30,
priority: 0,
},
},
{
name: "histogram-index-gt-32-bit-max",
input: "histogram-index=4294967296",
},

{
name: "conversion-sites-wrong-type",
input: "conversion-sites=a, histogram-index=1",
Expand All @@ -97,6 +114,22 @@ runTests([
name: "match-value-not-integer",
input: "match-value=1.2, histogram-index=1",
},
{
name: "valid-match-value-eq-32-bit-max",
input: "match-value=4294967295, histogram-index=1",
expected: {
histogramIndex: 1,
matchValue: 4294967295,
conversionSites: [],
conversionCallers: [],
lifetimeDays: 30,
priority: 0,
},
},
{
name: "match-value-gt-32-bit-max",
input: "match-value=4294967296, histogram-index=1",
},

{
name: "lifetime-days-wrong-type",
Expand All @@ -111,7 +144,51 @@ runTests([
input: "lifetime-days=1.2, histogram-index=1",
},
{ name: "lifetime-days-zero", input: "lifetime-days=0, histogram-index=1" },
{
name: "valid-lifetime-days-maximal-clamped",
input: "lifetime-days=999999999999999, histogram-index=1",
expected: {
histogramIndex: 1,
matchValue: 0,
conversionSites: [],
conversionCallers: [],
lifetimeDays: 4294967295,
priority: 0,
},
},

{ name: "priority-wrong-type", input: "priority=a, histogram-index=1" },
{ name: "priority-not-integer", input: "priority=1.2, histogram-index=1" },
{
name: "valid-priority-eq-32-bit-max",
input: "priority=2147483647, histogram-index=1",
expected: {
histogramIndex: 1,
matchValue: 0,
conversionSites: [],
conversionCallers: [],
lifetimeDays: 30,
priority: 2147483647,
},
},
{
name: "valid-priority-eq-32-bit-min",
input: "priority=-2147483648, histogram-index=1",
expected: {
histogramIndex: 1,
matchValue: 0,
conversionSites: [],
conversionCallers: [],
lifetimeDays: 30,
priority: -2147483648,
},
},
{
name: "priority-gt-32-bit-max",
input: "priority=2147483648, histogram-index=1",
},
{
name: "priority-lt-32-bit-min",
input: "priority=-2147483649, histogram-index=1",
},
]);
36 changes: 29 additions & 7 deletions impl/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import type { Dictionary } from "structured-headers";

import { parseDictionary } from "structured-headers";

const MAX_UINT32: number = 4294967295;

const MIN_INT32: number = -2147483648;
const MAX_INT32: number = 2147483647;

function parseInnerListOfSites(dict: Dictionary, key: string): string[] {
const [values] = dict.get(key) ?? [[]];
if (!Array.isArray(values)) {
Expand All @@ -29,9 +34,12 @@ export function parseSaveImpressionHeader(
if (
typeof histogramIndex !== "number" ||
!Number.isInteger(histogramIndex) ||
histogramIndex < 0
histogramIndex < 0 ||
histogramIndex > MAX_UINT32
) {
throw new RangeError("histogram-index must be a non-negative integer");
throw new RangeError(
"histogram-index must be an integer in the 32-bit unsigned range",
);
}

const conversionSites = parseInnerListOfSites(dict, "conversion-sites");
Expand All @@ -41,12 +49,15 @@ export function parseSaveImpressionHeader(
if (
typeof matchValue !== "number" ||
!Number.isInteger(matchValue) ||
matchValue < 0
matchValue < 0 ||
matchValue > MAX_UINT32
) {
throw new RangeError("match-value must be a non-negative integer");
throw new RangeError(
"match-value must be an integer in the 32-bit unsigned range",
);
}

const [lifetimeDays] = dict.get("lifetime-days") ?? [30];
let [lifetimeDays] = dict.get("lifetime-days") ?? [30];
if (
typeof lifetimeDays !== "number" ||
!Number.isInteger(lifetimeDays) ||
Expand All @@ -55,9 +66,20 @@ export function parseSaveImpressionHeader(
throw new RangeError("lifetime-days must be a positive integer");
}

if (lifetimeDays > MAX_UINT32) {
lifetimeDays = MAX_UINT32;
}

const [priority] = dict.get("priority") ?? [0];
if (typeof priority !== "number" || !Number.isInteger(priority)) {
throw new RangeError("priority must be an integer");
if (
typeof priority !== "number" ||
!Number.isInteger(priority) ||
priority < MIN_INT32 ||
priority > MAX_INT32
) {
throw new RangeError(
"priority must be an integer in the 32-bit signed range",
);
}

return {
Expand Down