w3c / json-ld-syntax

JSON-LD 1.1 Specification
https://w3c.github.io/json-ld-syntax/
Other
109 stars 38 forks source link

URI in Profile triggers CORS Unsafe Request Header Byte rule #436

Open azaroth42 opened 2 weeks ago

azaroth42 commented 2 weeks ago

In the IANA registration [1], we define a media type parameter called 'profile'. Its value is a space separated list of URIs, for which we registered six initial values. These can be composed together, and new values can be added for other "constraints or conventions".

The IIIF specifications use this functionality, for example to define the specific structure of the response in an API [2] as part of the media type. Similarly in Linked Art, we do the same [3].

However, in the WHATWG specification for fetch [4], it says that the value for the Accept header is NOT CORS safe, if it has more than 128 bytes (which multiple URIs might easily cause) or (more importantly) if the value contains an unsafe header byte. The unsafe header bytes include the character ":" ... which prevents any URI or CURIE with a namespace prefix separate by : from being CORS safe.

This means that we cannot use the JSON-LD media type as registered for content negotiation via the accept header according to the fetch specification, which was much of the rationale for the profile parameter.

To resolve this, either WHATWG would need to change fetch, or W3C/IANA would need to change the definition of the media type and give some registration function for possible profile values, then all downstream specifications would need to register a safe profile value to use.

I've added the tag-needs-resolution label, as I think that's the level this would need to run up to :(

[1] https://www.w3.org/TR/json-ld11/#iana-considerations [2] https://iiif.io/api/presentation/3.0/#63-responses [3] https://linked.art/api/1.0/json-ld/#introduction [4] https://fetch.spec.whatwg.org/#ref-for-cors-unsafe-request-header-byte

davidlehn commented 2 weeks ago

You say "NOT CORS safe", but I think the issue is the header will not be "CORS-safelisted"? When that is the case, a preflight request will be used. If that succeeds, the request should be sent. This doesn't seem like a blocker for using any of these headers, just a bit more server complexity than the "simple requests" CORS case that doesn't do a preflight. You probably need to setup a Access-Control-Allow-Headers to allow Accept, others as needed, and probably want Access-Control-Max-Age to cache preflight requests if appropriate.

@pchampin had a w3id.org issue with CORS and preflights the other day, where I learned more about this topic and safelists and simple requests and redirects. https://github.com/perma-id/w3id.org/issues/4185 and https://github.com/perma-id/w3id.org/pull/4196.

I tried a simple fetch from a browser console, and it does appear to do the above behavior. No profile will skip preflight, adding profile with URL will do one, then send the request. You do need a server setup to handle CORS headers, but then it works.

await fetch(
  "https://example.com/test",
  {headers: {"Accept": "application/ld+json;profile=http://www.w3.org/ns/json-ld#expanded"}}
)