A user account is required in order to edit this wiki, but we've had to disable public user registrations due to spam.

To request an account, ask an autoconfirmed user on IRC (such as one of these permanent autoconfirmed members).

Difference between revisions of "Fetch"

From WHATWG Wiki
Jump to: navigation, search
(Pseudo-code: add request flags)
(Pseudo-code)
Line 105: Line 105:
  
 
== Pseudo-code ==
 
== Pseudo-code ==
 +
 +
  // "No CORS" means no Origin header, no CORS checking, mark as "cross-origin", unless
 +
  // no_tainting is defined, in which case NetworkError()
 +
  // "Anonymous" means omit_credentials, normal CORS stuff; should probably mean no
 +
  // Referer and globally unique identifier for Origin
 +
  // "Use Credentials" means normal CORS stuff
  
 
   class Request:
 
   class Request:
Line 119: Line 125:
 
     force_preflight    // CORS prefligth required
 
     force_preflight    // CORS prefligth required
 
     force_same_origin  // HTML uses this for XMLDocument.load() and Workers
 
     force_same_origin  // HTML uses this for XMLDocument.load() and Workers
 +
    cors                // True / False for now
 
     omit_credentials    // set to True to omit cookies for cross-origin requests (HTTP authentication?)
 
     omit_credentials    // set to True to omit cookies for cross-origin requests (HTTP authentication?)
 
     no_tainting        // set to True to terminate in a "network_error" when tainted is set
 
     no_tainting        // set to True to terminate in a "network_error" when tainted is set
 
                         // XMLHttpRequest wants this, <track> too apparently
 
                         // XMLHttpRequest wants this, <track> too apparently
 
  
 
   class Response:
 
   class Response:
     state              // "fetching", "headers", "loading", "done"
+
     state              // "", "headers", "loading", "done"
 
     type                // "", "redirect", "network_error", "abort_error", "response"
 
     type                // "", "redirect", "network_error", "abort_error", "response"
 
     location            // if type == "redirect", a URL, None otherwise
 
     location            // if type == "redirect", a URL, None otherwise
Line 138: Line 144:
 
   class NetworkError(Response):
 
   class NetworkError(Response):
 
     ... state is "done", type is "network_error"
 
     ... state is "done", type is "network_error"
 
  def taint(response):
 
    response.cors = "cross-origin"
 
    return response
 
  
 
   def fetch(request):
 
   def fetch(request):
 
     url = request.url
 
     url = request.url
 
     origin = request.origin
 
     origin = request.origin
     if url.scheme in ("about", "blob", "data"):
+
     if url.origin == origin or url.scheme in ("about", "blob", "data"):
       return basic_fetch(request)
+
       return redirect_fetch(request)
     elif url.origin == origin:
+
    elif request.force_same_origin or (not request.cors and request.no_tainting):
       return same_origin_fetch(request)
+
      return NetworkError()
     elif request.force_same_origin
+
     elif not request.cors:
 +
       return taint(redirect_fetch(request)) // url.scheme == "file" end up here
 +
     elif url.scheme not in ("http", "https"):
 
       return NetworkError()
 
       return NetworkError()
    elif url.scheme == "file":
 
      ... implementation specific
 
    elif url.scheme == "ftp":
 
      return taint(basic_fetch(request))
 
 
     elif not requires_cors_with_preflight(request):
 
     elif not requires_cors_with_preflight(request):
       return potential_cors_fetch(request)
+
       return cors_fetch(request)
 
     else:
 
     else:
 
       return cors_fetch_with_preflight(request)
 
       return cors_fetch_with_preflight(request)
Line 181: Line 181:
 
       return NetworkError()
 
       return NetworkError()
  
   def same_origin_fetch (request):
+
   def redirect_fetch(request):
 
     response = basic_fetch(request)
 
     response = basic_fetch(request)
 
     response.wait_for_headers() // design something better
 
     response.wait_for_headers() // design something better
Line 187: Line 187:
 
       request.url = response.location
 
       request.url = response.location
 
       return fetch(request)
 
       return fetch(request)
 +
    elif response.type == "redirect":
 +
      request.url = response.location
 +
      return same_origin_fetch(request)
 +
    else:
 +
      return response
 +
 +
  def taint(response):
 +
    response.cors = "cross-origin"
 
     return response
 
     return response
  
Line 203: Line 211:
 
     ...
 
     ...
  
   def potential_cors_fetch(request):
+
   def cors_fetch(request):
 
     response = basic_fetch(request)
 
     response = basic_fetch(request)
 
     response.wait_for_headers()
 
     response.wait_for_headers()
Line 211: Line 219:
 
       request.url = response.location
 
       request.url = response.location
 
       if request.url.username or request.url.password or not cors_check(response):
 
       if request.url.username or request.url.password or not cors_check(response):
         request.tainted = True
+
         response.type = "network_error"
        if request.no_tainting:
+
        return response
          response.type = "network_error"
+
       return cors_fetch(request)
          return response
 
       return potential_cors_fetch(request)
 
 
     // XXX should this succeed for about: / blob: data: (think redirects)
 
     // XXX should this succeed for about: / blob: data: (think redirects)
     if not request.tainted and cors_check(response):
+
     elif response.type == "response" and not cors_check(response):
       response.cors = "same-origin"
+
       response.type = "network_error
 
     return response
 
     return response
  
Line 226: Line 232:
 
     response = basic_fetch(request)
 
     response = basic_fetch(request)
 
     response.wait_for_headers()
 
     response.wait_for_headers()
     if response.type in ("redirect", "network_error"):
+
     if response.type in ("redirect", "network_error") or (response.type == "response" and not cors_check(response)):
 
       clear_cors_preflight_cache(request)
 
       clear_cors_preflight_cache(request)
 
       response.type = "network_error"
 
       response.type = "network_error"
      return response
+
     return response
     elif response.type == "abort_error":
 
      return response
 
    if cors_check(response):
 
      return response
 
    else:
 
      clear_cors_preflight_cache(request)
 
      response.type = "network_error"
 
      return response
 
  
 
   def in_cors_preflight_cache(request):
 
   def in_cors_preflight_cache(request):

Revision as of 12:53, 27 February 2013

The contents of this page, Fetch, and all edits made to this page in its history, are hereby released under the CC0 Public Domain Dedication, as described in WHATWG Wiki:Copyrights.

Plan

Fetch is fetch.spec.whatwg.org and will define HTML fetch and CORS as a set of coherent algorithms rather than the intertwined mess we have now.

Goals

  • Centralize redirect handling
  • Centralize CORS
  • Centralize fetching and thereby consistify handling of e.g. data URLs across APIs
  • Allow resources to opt into CORS without the API asking for it (stop requiring a crossorigin attribute, remove "No CORS" mode)

Model

The basic model is Request -> Fetch -> Response.

Request

  • Parsed URL (object)
  • method (probably with restrictions as seen in XHR)
  • UA headers
  • author headers (maybe rename because people get upset with "author", with implicit restrictions as seen in XHR / CORS)
  • entity body
  • origin (object)
  • referrer source (Document / URL)
  • manual redirect flag
  • omit credentials flag (will replace HTML fetch block cookies flag but also has other features)
  • force preflight flag (set for upload progress notifications (to not reveal existence of server in case of POST I suppose; see bug 20322))
  • synchronous flag
  • force same-origin flag (looks identical to No CORS, fail, filed bug 20951)
  • CORS mode
    • No CORS, taint (<link>, <script>, ...); still need to allow the server to opt in to CORS anyway to effectively make the resource CORS same-origin even if not requested as such (HTML does not have this feature
    • No CORS, fail (<track>)
    • Anonymous
    • Credentialed

Basic Fetch

See also URL. Fetch specifics depends on the URL scheme. Where possible fetching happens incrementally. If it completely fails you get a network error. Otherwise a response. A response might be exposed from the moment status/headers are available and the entity body is still loading.

about

See URL schemes.

blob

http://dev.w3.org/2006/webapi/FileAPI/#processingModel

data

Response object as per http://xhr.spec.whatwg.org/#data:-urls-and-http

If the data URL fails to parse however return a network error.

file

This is by and large platform-specific.

ftp

Construct the request per the FTP specification, do the request, and expose the response.

http and https

  • Construct the request per the HTTP specification
  • Do the request
  • Expose the response.

any other scheme

Network error.

Fetch

Basic Fetch deals with a single request/response whereas Fetch tackles the more complicated setups required by http/https:

  • Redirects
    • We want to follow them if the status code is 301, 302, 303, 307, 308 and there's a Location header (and if there's more than one Location header we need to pick probably), unless a flag is set to not follow redirects).
    • We need to carefully consider what happens if the new Location is not same-origin and especially what if it's not http or https. That might depend on the API.
  • Authentication (challenge request, prompt the user for certain legacy APIs)
  • CORS (preflight requests, marking the response as same-origin or not)
  • Cookies
    • Include them in the request or not?
    • Deal with cookies in the response (before handling redirects and such) and the storage mutex.
  • 304 needs to be turned into a 200 for XMLHttpRequest unless explicitly requested otherwise. We should make it clearer how that works.

For everything else Fetch will simply defer to Basic Fetch, but Fetch will be the algorithm used by the whole platform.

Response

Both intermediate updates (progress, headers received, ...) and final. Also indicates network error / CORS error (exposed as network error), ...

More importantly, it expresses everything in terms of HTTP, regardless of whether the request was for file/blob/data/etc. For this we need to expose:

  • Status code
  • Status text
  • Headers
  • Entity body

User agent advice

For http/https, maybe give advice on proxies, e.g.: "If the user agent allows the end user to configure a proxy it should modify the request appropriately; i.e., connect to the proxy host instead of the origin server, modify the Request-Line and send Proxy-Authorization headers as specified." Though ideally HTTP does this.

For http/https, advice to not include to much random headers, even though it'll happen to some extent anyway.

Pseudo-code

 // "No CORS" means no Origin header, no CORS checking, mark as "cross-origin", unless
 // no_tainting is defined, in which case NetworkError()
 // "Anonymous" means omit_credentials, normal CORS stuff; should probably mean no
 // Referer and globally unique identifier for Origin
 // "Use Credentials" means normal CORS stuff
 class Request:
   url
   origin              // currently "source origin" in some specs
   referrer_source
   method              // e.g. GET
   author_headers = []
   ua_headers = []
   headers = []        // unholy union of author_headers and ua_headers
   entity_body
   tainted             // during redirects the request can get tainted meaning
                       // eventual Response.cors must be "cross-origin"
   force_preflight     // CORS prefligth required
   force_same_origin   // HTML uses this for XMLDocument.load() and Workers
   cors                // True / False for now
   omit_credentials    // set to True to omit cookies for cross-origin requests (HTTP authentication?)
   no_tainting         // set to True to terminate in a "network_error" when tainted is set
                       // XMLHttpRequest wants this, <track> too apparently
 class Response:
   state               // "", "headers", "loading", "done"
   type                // "", "redirect", "network_error", "abort_error", "response"
   location            // if type == "redirect", a URL, None otherwise
   redirect_count      // to prevent loops just terminate when this is 10 or so
   status_code
   status_text
   headers
   headers_exposed     // not all headers can be exposed to web-facing APIs
   entity_body
   cors                // "same-origin" or "cross-origin"
 class NetworkError(Response):
   ... state is "done", type is "network_error"
 def fetch(request):
   url = request.url
   origin = request.origin
   if url.origin == origin or url.scheme in ("about", "blob", "data"):
     return redirect_fetch(request)
   elif request.force_same_origin or (not request.cors and request.no_tainting):
     return NetworkError()
   elif not request.cors:
     return taint(redirect_fetch(request)) // url.scheme == "file" end up here
   elif url.scheme not in ("http", "https"):
     return NetworkError()
   elif not requires_cors_with_preflight(request):
     return cors_fetch(request)
   else:
     return cors_fetch_with_preflight(request)
 def basic_fetch(request):
   url = request.url
   if url.scheme == "about":
     if url.scheme_data == "blank":
       return Response({type:"text/html", encoding:"utf-8", body:""})
     else:
       return NetworkError()
   elif url.scheme == "blob":
     ...
   elif url.scheme == "data":
     ...
   elif url.scheme == "file":
     ...
   elif url.scheme == "ftp":
     ...
   elif url.scheme in ("http", "https"):
     ... count redirects
   else:
     return NetworkError()
 def redirect_fetch(request):
   response = basic_fetch(request)
   response.wait_for_headers() // design something better
   if response.type == "redirect" and response.location.origin != request.url.origin:
     request.url = response.location
     return fetch(request)
   elif response.type == "redirect":
     request.url = response.location
     return same_origin_fetch(request)
   else:
     return response
 def taint(response):
   response.cors = "cross-origin"
   return response
 def requires_cors_with_preflight(request):
   if request.url.scheme not in ("http", "https")
     return False
   if request.force_preflight:
     return True
   if request.method not in cors_simple_methods:
     return True
   for header in request.author_headers:
     if not is_cors_simple_header(header):
       return True
 def cors_simple_header(header):
   ...
 def cors_fetch(request):
   response = basic_fetch(request)
   response.wait_for_headers()
   if response.type == "redirect":
     if request.url.origin != response.location.origin:
       request.origin = uuid()
     request.url = response.location
     if request.url.username or request.url.password or not cors_check(response):
       response.type = "network_error"
       return response
     return cors_fetch(request)
   // XXX should this succeed for about: / blob: data: (think redirects)
   elif response.type == "response" and not cors_check(response):
     response.type = "network_error
   return response
 def cors_fetch_with_preflight(request):
   if not in_cors_preflight_cache(request):
     ... preflight_dance
   response = basic_fetch(request)
   response.wait_for_headers()
   if response.type in ("redirect", "network_error") or (response.type == "response" and not cors_check(response)):
     clear_cors_preflight_cache(request)
     response.type = "network_error"
   return response
 def in_cors_preflight_cache(request):
   ...
 def clear_cors_preflight_cache(request):
   ...
 def cors_check(response):
   ...