Skip to content

Mark usage sharing values with replaced characters as escaped - #54

Merged
jwrosewell merged 14 commits into
rcfrom
align/usage-record
Nov 10, 2026
Merged

jwrosewell merged 14 commits into
rcfrom
align/usage-record

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Brings the usage sharing record in line with the Processing section of the Usage Sharing Element page and the record decisions in specifications#122 (merged into rc). Both were read in full on rc.

The page says that "escaped - Indicates that the original evidence value included invalid characters, which have been replaced with escaped ones." and "truncated - Indicates that the original evidence value was too long." The record on rc shows them as <PREFIX [escaped="true"] [truncated="true"] Name="FIELD">VALUE</PREFIX>.

What changed

  • A value that had characters replaced is marked escaped="true", where shareUsage.js wrote replaced="true".
  • A character XML 1.0 does not allow is written as a backslash, x and the four lower case hexadecimal digits of its code, for example \x001f, as the .NET and Go elements do. The code wrote U+FFFD, and its list of allowed characters was wrong (it treated tab and line feed as invalid, and every character above U+00FF as invalid). XML 1.0 allows tab, line feed, carriage return, U+0020 to U+D7FF, U+E000 to U+FFFD and U+10000 to U+10FFFF, and a surrogate pair is one allowed character. A lone surrogate is written as \x and its four digits.
  • The limit is 10,000 Unicode code points, where it was 512 characters counted after escaping. It counts the value as supplied and cuts first, then escapes, so the escapes do not count towards it and a character made of two UTF-16 units is never cut in two. A value cut is marked truncated="true". The constant is SHARE_USAGE_MAX_EVIDENCE_CODE_POINTS, so its name gives the unit.
  • No CDATA and no BadSchema element are written, as before.
  • SessionId, Sequence, ClientIP and the Name attribute go through the same escaping as evidence values (addToShareUsage). The session id comes from query.session-id, which the browser sends, and the client IP from the request, so a value holding < or & made the whole batch invalid XML.
  • A request with no client IP has an empty <ClientIP></ClientIP>, where it had <ClientIP>undefined</ClientIP>, because ShareUsageData set clientIp and the writer read clientIP. The record says "Things in [square brackets] are optional", and <ClientIP>IP_ADDRESS</ClientIP> has no brackets. .NET writes the element empty when there is no value (WriteElementString("ClientIP", data.ClientIP)).
  • Every record now has <ServerIP>, which the record lists without brackets and which .NET (ShareUsageBase.cs, HostAddress) and Java (getHostAddress) write and Node did not. As in .NET, serverAddress gives the first address of this server that is not a loopback address, an IPv4 address before an IPv6 one, or an empty string where there is none. Node reads the addresses with os.networkInterfaces(), where .NET resolves the host name, and the value is worked out once with the rest of the fixed part of the record.
  • ReplacedString writes a value that is not a string as its text, and undefined and null as nothing. Evidence values already reach it as text, because tryAddToData calls value.toString(), so the tests that a number, zero and false are shared as their text pass on the old code too, as guards. The change matters for Sequence, which is a number and now goes through ReplacedString.

Open draft #45 changes shareUsage.js and its tests in the evidence handling, and neither it nor #36 touches the lines changed here. The web integration draft #53 adds one test to shareUsage.test.js away from the lines changed here.

Tests

The invalid character, long value, emoji at the limit, escapes-do-not-count and escaped-and-truncated cases are in tests/shareUsage.test.js. The test server decoded each chunk of the body on its own and split an emoji in two, so it now decodes as the chunks arrive. New since the last review are the XML characters in the session id, the client IP and an evidence name, an empty ClientIP for a request with no client IP, ServerIP for a server with IPv4 and IPv6 addresses, with IPv6 alone and with loopback addresses alone, and a number, zero and false as evidence.

macOS was left out of this draft's full build under the 9 November decision. The rc head is built with macOS before it merges into main.

Not done here

An evidence key with no prefix, such as foo rather than header.foo, is written with its name as the element name, and an element name cannot be escaped. That is unchanged here. The elements are also written in a different order from the record, the fixed part first, as .NET writes its fixed part after ClientIP rather than in the record's order. Nothing else on the page differs from the Node code that was checked.

The Usage Sharing Element page names the attribute escaped, where the
Node element wrote replaced.
…t values at 10,000 characters

The Usage Sharing Element page marks a changed value escaped or truncated.
A character XML 1.0 does not allow is now written as a backslash, x and the
four lower case hexadecimal digits of its code, as the .NET and Go
elements do, and the limit is 10,000 characters, cut from the original value.
…idence names

Evidence that is not a string must be shared as its text rather than as
an empty value. The session id and the client IP come from the request,
and an evidence name is written in an attribute, so the XML characters
in each must be escaped. A request with no client IP must have no
ClientIP element rather than one holding undefined.
Evidence that is not a string is shared as its text, as .NET writes
it, where a number became an empty value. The session id, the sequence,
the client IP and the Name attribute go through the same escaping as
evidence values, because the session id and the client IP come from the
request, so a value holding < or & made the whole batch invalid XML. A
request with no client IP now has no ClientIP element, where it had one
holding undefined, as Java leaves it out. The 10,000 limit's constant
names its unit, code points.
The specification's record marks only the bracketed items as optional,
and ClientIP and ServerIP have no brackets, so a request with no client
IP must have an empty ClientIP element, as .NET writes it, and every
record must have a ServerIP element. The ServerIP tests give the server
an IPv4 and an IPv6 address, an IPv6 address alone, and loopback
addresses alone.
The specification's record marks only the bracketed items as optional,
and ClientIP and ServerIP have no brackets. A request with no client IP
now has an empty ClientIP element, as .NET writes it, where it had none.
Every record now has the ServerIP element, which .NET and Java write and
Node did not. Like .NET, it is the first address of this server that is
not a loopback address, an IPv4 address before an IPv6 one, or empty
where there is none.
@jwrosewell
jwrosewell marked this pull request as ready for review November 10, 2026 16:37
@jwrosewell
jwrosewell merged commit f88193d into rc Nov 10, 2026
2 checks passed
@jwrosewell
jwrosewell deleted the align/usage-record branch November 10, 2026 16:37
@jwrosewell jwrosewell mentioned this pull request Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant