-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
URL serialiser does not produce idempotent strings if an opaque path has a trailing space and "exclude fragment" is true #784
Comments
To illustrate using JSDOM: const urlRecord = parseURL("data:space #hello");
const serialized = serializeURL(urlRecord, /* excludeFragment: */ true);
const reparsed = parseURL(serialized);
const serializedAgain = serializeURL(reparsed, /* excludeFragment: */ true);
assert.strictEqual(serialized, serializedAgain); // Fails
|
Nice catch! We could also try to encode a trailing space, maybe. Or maybe we already considered and discarded that last time for a good reason? Within a browser serializing without a fragment is mainly done for networking-related code (and thus |
I don't see any discussion last time about escaping trailing spaces. I suggested escaping all spaces, but that was considered likely incompatible with the web. I think it should be fine. That said, I would suggest that we escape all trailing spaces (not just the final trailing space). I can't imagine that escaping just the final space would be any better for compatibility. |
I'd prefer to just escape only the last trailing space. It's simpler and the algorithm would be faster. |
@achristensen07 @valenting @hayatoito thoughts on how to best tackle this? Given the discussion so far I see these options:
And then variants of 1/2/3 where we instead replace all trailing spaces with I like the simplicity of 1 personally. |
So, I think there is probably broad agreement that unescaped spaces are not ideal. Unfortunately, they are required for web compatibility, so we have to allow them as much as possible. As part of this change, one or more trailing spaces that would previously have been unescaped would now be escaped. That's a change which has the potential to break some applications, but it is equally breaking whether we escape one trailing space or all trailing spaces. There is no compatibility advantage to only escaping a single space - the same applications would break either way, and the remedy would be the same. I can't think of another time where we escape only a single occurrence of a particular character, and leave all other instances of that character in the same URL component without escaping; generally it's always "code point X is escaped in component Y". So both possibilities add not-insignificant complexity - but, if I were a developer working these kinds of URLs, I think it would be overall simpler and more predictable if the parser escaped all of those trailing spaces for me at once. Then I could do my processing (perhaps removing some of those trailing spaces), and I wouldn't keep seeing the parser add escaping for me all the time. As for performance? The difference is negligible. We're talking about an edge case of an edge case of an edge case (multiple occurrences of unescaped trailing spaces in a URL with opaque path); the important thing is that the more common scenarios can be fast-pathed, and they can. |
@karwa however, the current proposal gives |
Right. The question is only about multiple trailing unescaped spaces. If the source only contains a single trailing space to begin with, the result would be the same.
I think escaping all trailing spaces leads to a simpler and more predictable outcome (and fewer unescaped spaces, even if we can't escape all of them) -- but again, it's an edge case of an edge case of an edge case, so it's not extremely important to me. |
https://software.hixie.ch/utilities/js/live-dom-viewer/saved/12022 demonstrates this issue using XMLHttpRequest. Gecko strips trailing spaces and Chromium/WebKit do not (none escape). Perhaps this lack of interoperability suggests we have some freedom here to do something better? @achristensen07 @valenting @hayatoito thoughts? |
While I would prefer a consistent behaviour such as escaping all spaces in a path, I suspect it might be easier to just escape or strip the trailing spaces. I don't have any preference between the two. |
I haven't had time to understand the issue yet. Can't we consider that encoding a trailing space is a user's responsibility, instead of URL Standard's responsibility? For example, the following is a bad practice. Spaces can be trimmed anytime after some accidental operations.
Thus, we encourage users to escape trailing spaces by themselves before passing it to URL to avoid an accidental removal.
Please correct me if I don't understand the issue. |
You understand it correctly, the problem here is that we have an idempotence goal: https://url.spec.whatwg.org/#goals. And while the goals are not immutable, idempotence is a property I think we want to keep. There have been quite a few security issues with URL implementations that do not have that property. |
Thanks! I didn't notice that idempotence is clearly stated as a goal in the URL Standard. I understand now.
Proposal 1 means:
, right? This sounds best to me (out of 1/2/3/1b/2b/3b). However, as far as I understand, the following URLs (as a result of serialization) are not equivalent to each other:
So every option proposed here seems a technically breaking change.
This doesn't seem a popular option here, however, this looks the simplest and easy-to-understand rule to me. I assume we introduce a breaking change anyway. |
I’d be fine with this, too. |
In fdaa0e5 we tackled a problem whereby removing the fragment or query from a URL with an opaque path through the API would not make the URL roundtrip due to the opaque path being able to end in non-percent-encoded spaces. However, this failed to address other ways of serializing the URL. As such this is a new approach whereby opaque paths simply cannot end with non-percent-encoded spaces. Enforcing this in the URL parser allows us to completely revert the aforementioned commit, greatly simplifying the API implementation. Fixes #784.
I put up #844 which removes trailing U+0020 from opaque paths during parsing. Now that all browsers have a good handle on non-special URLs it seems like a good time to be fixing this (modulo having to wait a bit for Interop 2024 results to be frozen). |
A URL's opaque path can contain trailing spaces if there is a query or fragment after it
This presents a problem, because if the URL somehow loses its query or fragment, the URL's serialisation will have trailing spaces. Since the parser trims spaces, re-parsing such a URL would not return an equivalent value to one that was serialised.
In Setters can cause failure to roundtrip #651 we modified the query and fragment setters to safeguard against this - if you use the API to remove the query/fragment, trailing spaces in the opaque path are trimmed.
However, the URL serialiser also has an "exclude fragment" parameter (default false). If this parameter were to be set true, it would produce the same issue as Setters can cause failure to roundtrip #651
Fixing this will be tricky. I think there are only a couple of options:
We just document the status quo; the API/parser/serialiser are idempotent except when "exclude fragment" is set to true, in which case they might corrupt the
path
. That doesn't sound good. It's quite common to exclude the fragment.We trim opaque paths when serialising. That's also not good, because it means the API tells me one thing, but somebody (such as a browser) could serialise the URL without fragment, reparse the result, and the same API property would have a different value.
We always trim trailing spaces from opaque paths. Technically a breaking change, but overall it's better at ensuring everything stays consistent.
The text was updated successfully, but these errors were encountered: