INTERNET DRAFT Michiel B. de Jong
+Document: draft-dejong-remotestorage-04 IndieHosters
+ F. Kooman
+Intended Status: Proposed Standard (independent)
+Expires: 18 June 2015 15 December 2014
+
+
+ remoteStorage
+
+Abstract
+
+ This draft describes a protocol by which client-side applications,
+ running inside a web browser, can communicate with a data storage
+ server that is hosted on a different domain name. This way, the
+ provider of a web application need not also play the role of data
+ storage provider. The protocol supports storing, retrieving, and
+ removing individual documents, as well as listing the contents of an
+ individual folder, and access control is based on bearer tokens.
+
+Status of this Memo
+
+ This Internet-Draft is submitted in full conformance with the
+ provisions of BCP 78 and BCP 79.
+
+ Internet-Drafts are working documents of the Internet Engineering
+ Task Force (IETF). Note that other groups may also distribute
+ working documents as Internet-Drafts. The list of current Internet-
+ Drafts is at http://datatracker.ietf.org/drafts/current/.
+
+ Internet-Drafts are draft documents valid for a maximum of six months
+ and may be updated, replaced, or obsoleted by other documents at any
+ time. It is inappropriate to use Internet-Drafts as reference
+ material or to cite them other than as "work in progress."
+
+ This Internet-Draft will expire on 15 December 2014.
+
+Copyright Notice
+
+ Copyright (c) 2014 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+de Jong [Page 1]
+
Internet-Draft remoteStorage December 2014
+
+
+Table of Contents
+
+ 1. Introduction...................................................22. Terminology....................................................33. Storage model..................................................34. Requests.......................................................45. Response codes.................................................76. Versioning.....................................................77. CORS headers...................................................88. Session description............................................89. Bearer tokens and access control...............................910. Application-first bearer token issuance.......................1011. Storage-first bearer token issuance...........................1112. Example wire transcripts......................................1212.1. WebFinger................................................1212.2. OAuth dialog form........................................1312.3. OAuth dialog form submission.............................1412.4. OPTIONS preflight........................................1512.5. Initial PUT..............................................1512.6. Subsequent PUT...........................................1612.7. GET......................................................1612.8. DELETE...................................................1713. Distributed versioning........................................1714. Security Considerations.......................................1915. IANA Considerations...........................................2016. Acknowledgments...............................................2017. References....................................................2117.1. Normative References.....................................2117.2. Informative References...................................2118. Authors' addresses............................................221. Introduction
+
+ Many services for data storage are available over the internet. This
+ specification describes a vendor-independent interface for such
+ services. It is based on https, CORS and bearer tokens. The
+ metaphor for addressing data on the storage is that of folders
+ containing documents and subfolders. The actions the interface
+ exposes are:
+
+ * GET a folder: retrieve the names and current versions of the
+ documents and subfolders currently contained by the folder
+
+
+de Jong [Page 2]
+
Internet-Draft remoteStorage December 2014
+
+
+ * GET a document: retrieve its content type, current version,
+ and contents
+
+ * PUT a document: store a new version, its content type, and
+ contents, conditional on the current version
+
+ * DELETE a document: remove it from the storage, conditional on
+ the current version
+
+ * HEAD a folder or document: like GET, but omitting the response
+ body
+
+ The exact details of these four actions are described in this
+ specification.
+
+2. Terminology
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in RFC 2119 [WORDS].
+
+ "SHOULD" and "SHOULD NOT" are appropriate when valid exceptions to a
+ general requirement are known to exist or appear to exist, and it is
+ infeasible or impractical to enumerate all of them. However, they
+ should not be interpreted as permitting implementors to fail to
+ implement the general requirement when such failure would result in
+ interoperability failure.
+
+3. Storage model
+
+ The server stores data in nodes that form a tree structure.
+ Internal nodes are called 'folders' and leaf nodes are called
+ 'documents'. For a folder, the server stores references to nodes
+ contained in the folder, and it should be able to produce a list of
+ them, with for each contained item:
+
+ * item name
+ * item type (folder or document)
+ * current version
+ * content type
+ * content length
+
+ For a document, the server stores, and should be able to produce:
+
+
+de Jong [Page 3]
+
Internet-Draft remoteStorage December 2014
+
+
+
+ * current version
+ * content type
+ * content length
+ * content
+
+4. Requests
+
+ Client-to-server requests SHOULD be made over https [HTTPS], and
+ servers MUST comply with HTTP/1.1 [HTTP]. Specifically, they
+ MUST support chunked transfer coding on PUT requests. Servers MAY
+ also offer an optional switch from https to SPDY [SPDY].
+
+ A request is considered successful if the HTTP response code is in
+ the 2xx range (e.g. 200 OK, 201 Created), and unsuccessful if an
+ error occurred or a condition was not met (response code e.g. 404
+ Not Found, 304 Not Modified).
+
+ The root folder of the storage tree is represented by the following
+ URL:
+
+ URI_ENCODE( <storage_root> '/' )
+
+ Subsequently, if <parent_folder> is the URL of a folder, then the
+ URL of an item contained in it is:
+
+ URI_ENCODE( <parent_folder> <document_name> )
+
+ for a document, or:
+
+ URI_ENCODE( <parent_folder> <folder_name> '/' )
+
+ for a folder. Item names MAY contain all characters except '/' and
+ the null character, and MUST NOT have zero length.
+
+ A document description is a map containing one string-valued 'ETag'
+ field, one string-valued 'Content-Type' and one integer-valued
+ 'Content-Length' field. They represent the document's current
+ version, its content type, and its content length respectively. Note
+ that content length is measured in octets (bytes), not in
+ characters.
+
+ A folder description is a map containing a string-valued 'ETag'
+
+
+de Jong [Page 4]
+
Internet-Draft remoteStorage December 2014
+
+
+ field, representing the folder's current version.
+
+ A successful GET request to a folder MUST be responded to with a
+ JSON-LD [JSON-LD] document (content type 'application/ld+json'),
+ containing as its 'items' field a map in which contained documents
+ appear as entries <item_name> to a document description, and
+ contained non-empty folders appear as entries <item_name> '/' to a
+ folder description. It MUST also contain an '@context' field with
+ the value 'http://remotestorage.io/spec/folder-description'. For
+ instance:
+
+ {
+ "@context": "http://remotestorage.io/spec/folder-description",
+ "items": {
+ "abc": {
+ "ETag": "DEADBEEFDEADBEEFDEADBEEF",
+ "Content-Type": "image/jpeg",
+ "Content-Length": 82352
+ },
+ "def/": {
+ "ETag": "1337ABCD1337ABCD1337ABCD"
+ }
+ }
+ }
+
+ All folders are treated as existing, and therefore GET requests to
+ untouched folders SHOULD be responded to with a folder description
+ with no items (the items field set to '{}'). However, an empty
+ folder MUST NOT be listed as an item in its parent folder.
+
+ Also, since folders exist automatically, PUT and DELETE requests
+ only need to be made to documents, and never to folders. A document
+ PUT will make all ancestor folders along its path become non-empty;
+ deleting the last document from a subtree will make that whole
+ subtree become empty. Folders will therefore show up in their parent
+ folder descriptions if and only if their subtree contains at least
+ one document.
+
+ A successful GET request to a document SHOULD be responded to with
+ the full document contents in the body, the document's content type
+ in a 'Content-Type' header, its content length in octets (not in
+ characters) in a 'Content-Length' header, and the document's current
+ version as a strong ETag in an 'ETag' header.
+
+
+de Jong [Page 5]
+
Internet-Draft remoteStorage December 2014
+
+
+
+ Note that the use of strong ETags prohibits changing the response
+ body based on request headers; in particular, the server will not be
+ able to serve the same document uncompressed to some clients and
+ gzipped when requested by the client, since the two bodies would not
+ be identical byte-for-byte.
+
+ Servers MAY support Content-Range headers [RANGE] on GET requests,
+ but whether or not they do SHOULD be announced through the <ranges>
+ variable mentioned below in section 10.
+
+ A successful PUT request to a document MUST result in:
+
+ * the request body being stored as the document's new content,
+ * parent and further ancestor folders being silently created as
+ necessary, with the document (name and version) being added to
+ its parent folder, and each folder added to its subsequent
+ parent,
+ * the value of its Content-Type header being stored as the
+ document's new content type,
+ * its version being updated, as well as that of its parent folder
+ and further ancestor folders, using a strong validator [HTTP,
+ section 7.2].
+
+ The response MUST contain a strong ETag header, with the document's
+ new version (for instance a hash of its contents) as its value.
+
+ A successful DELETE request to a document MUST result in:
+
+ * the deletion of that document from the storage, and from its
+ parent folder,
+ * silent deletion of the parent folder if it is left empty by
+ this, and so on for further ancestor folders,
+ * the version of its parent folder being updated, as well as that
+ of further ancestor folders.
+
+ A successful OPTIONS request SHOULD be responded to as described in
+ the CORS section below.
+
+ A successful HEAD request SHOULD be responded to like to the
+ equivalent GET request, but omitting the response body.
+
+
+
+
+de Jong [Page 6]
+
Internet-Draft remoteStorage December 20145. Response codes
+
+ Response codes SHOULD be given as defined by [HTTP, section 6] and
+ [BEARER, section 3.1]. The following is a non-normative checklist
+ of status codes that are likely to occur in practice:
+
+ * 500 if an internal server error occurs,
+ * 429 if the client makes too frequent requests or is suspected
+ of malicious activity,
+ * 414 if the request URI is too long,
+ * 416 if Range requests are supported by the server and the Range
+ request can not be satisfied,
+ * 401 for all requests that don't have a bearer token with
+ sufficient permissions,
+ * 404 for all DELETE and GET requests to documents that do not
+ exist on the storage,
+ * 304 for a conditional GET request whose pre-condition
+ fails (see "Versioning" below),
+ * 409 for a PUT request where any folder name in the path
+ clashes with an existing document's name at the same
+ level, or where the document name coincides with an
+ existing folder's name at the same level.
+ * 412 for a conditional PUT or DELETE request whose pre-condition
+ fails (see "Versioning" below),
+ * 507 in case the account is over its storage quota,
+ * 4xx for all malformed requests (e.g. foreign characters in the
+ path), as well as for all PUT and DELETE requests to
+ folders,
+ * 2xx for all successful requests.
+
+ Clients SHOULD also handle the case where a response takes too long
+ to arrive, or where no response is received at all.
+
+6. Versioning
+
+ All successful requests MUST return an 'ETag' header [HTTP] with, in
+ the case of GET, the current version, in the case of PUT, the new
+ version, and in case of DELETE, the version that was deleted. All
+ successful GET requests MUST return an 'Expires: 0' header. PUT and
+ DELETE requests MAY have an 'If-Match' request header [COND], and
+ MUST fail with a 412 response code if that doesn't match the
+ document's current version.
+
+
+
+de Jong [Page 7]
+
Internet-Draft remoteStorage December 2014
+
+
+ GET requests MAY have a comma-separated list of revisions in an
+ 'If-None-Match' header [COND], and SHOULD be responded to with a 304
+ response if that list includes the document or folder's current
+ version. A PUT request MAY have an 'If-None-Match: *' header [COND],
+ in which case it MUST fail with a 412 response code if the document
+ already exists.
+
+ In all 'ETag', 'If-Match' and 'If-None-Match' headers, revision
+ strings should appear inside double quotes (").
+
+ A provider MAY offer version rollback functionality to its users,
+ but this specification does not define the user interface for that.
+
+7. CORS headers
+
+ All responses MUST carry CORS headers [CORS]. The server MUST also
+ reply to OPTIONS requests as per CORS. For GET requests, a wildcard
+ origin MAY be returned, but for PUT and DELETE requests, the
+ response MUST echo back the Origin header sent by the client.
+
+8. Session description
+
+ The information that a client needs to receive in order to be able
+ to connect to a server SHOULD reach the client as described in the
+ 'bearer token issuance' sections below. It consists of:
+
+ * <storage_root>, consisting of 'https://' followed by a server
+ host, and optionally a server port and a path prefix as per
+ [IRI]. Examples:
+ * 'https://example.com' (host only)
+ * 'https://example.com:8080' (host and port)
+ * 'https://example.com/path/to/storage' (host, port and
+ path prefix; note there is no trailing slash)
+ * <access_token> as per [OAUTH]. The token SHOULD be hard to
+ guess and SHOULD NOT be reused from one client to another. It
+ can however be reused in subsequent interactions with the same
+ client, as long as that client is still trusted. Example:
+ * 'ofb24f1ac3973e70j6vts19qr9v2eei'
+ * <storage_api>, always 'draft-dejong-remotestorage-04' for this
+ alternative version of the specification.
+
+ The client can make its requests using https with CORS and bearer
+ tokens, to the URL that is the concatenation of <storage_root> with
+
+
+de Jong [Page 8]
+
Internet-Draft remoteStorage December 2014
+
+
+ '/' plus one or more <folder> '/' strings indicating a path in the
+ folder tree, followed by zero or one <document> strings, indicating
+ a document. For example, if <storage_root> is
+ "https://storage.example.com/bob", then to retrieve the folder
+ contents of the /public/documents/ folder, or to retrieve a
+ 'draft.txt' document from that folder, the client would make
+ requests to, respectively:
+
+ * https://storage.example.com/bob/public/documents/
+ * https://storage.example.com/bob/public/documents/draft.txt
+
+9. Bearer tokens and access control
+
+ A bearer token represents one or more access scopes. These access
+ scopes are represented as strings of the form <module> <level>,
+ where the <module> string SHOULD be lower-case alphanumerical, other
+ than the reserved word 'public', and <level> can be ':r' or ':rw'.
+ The access the bearer token gives is the sum of its access scopes,
+ with each access scope representing the following permissions:
+
+ '*:rw') any request,
+
+ '*:r') any GET or HEAD request,
+
+ <module> ':rw') any requests to paths that start with
+ '/' <module> '/' or '/public/' <module> '/',
+
+ <module> ':r') any GET or HEAD requests to paths that start with
+ '/' <module> '/' or '/public/' <module> '/',
+
+ As a special exceptions, GET requests to a document (but not a
+ folder) whose path starts with '/public/' are always allowed. They,
+ as well as OPTIONS requests, can be made without a bearer token.
+ Unless [KERBEROS] is used (see section 10 below), all other requests
+ SHOULD present a bearer token with sufficient access scope, using a
+ header of the following form (no double quotes here):
+
+ Authorization: Bearer <access_token>
+
+ In addition, providing the access token via a HTTP query parameter
+ for GET requests MAY be supported by the server, although its use
+ is not recommended, due to its security deficiencies; see [BEARER,
+ section 2.3].
+
+
+de Jong [Page 9]
+
Internet-Draft remoteStorage December 201410. Application-first bearer token issuance
+
+ To make a remoteStorage server available as 'the remoteStorage of
+ <account> at <host>', exactly one link of the following format
+ SHOULD be added to the WebFinger record [WEBFINGER] of <account> at
+ <host>:
+
+ {
+ "href": <storage_root>,
+ "rel": "remotestorage",
+ "properties": {
+ "http://remotestorage.io/spec/version": <storage_api>,
+ "http://tools.ietf.org/html/rfc6749#section-4.2": <auth-dialog>,
+ ... : ... ,
+ }
+ }
+
+ Here <storage_root> and <storage_api> are as per "Session
+ description" above, and <auth-dialog> SHOULD be either null or a
+ URL where an OAuth 2.0 implicit-grant flow dialog [OAUTH] is
+ presented.
+
+ If <auth-dialog> is a URL, the user can supply their credentials
+ for accessing the account (how, is out of scope), and allow or
+ reject a request by the connecting application to obtain a bearer
+ token for a certain list of access scopes. Note that an account
+ will often belong to just one human user, but may also belong to a
+ group of multiple users (the remoteStorage of <group> at <host>).
+
+ If <auth-dialog> is null, the client will not have a way to obtain
+ an access token, and SHOULD send all requests without Authorization
+ header, and rely on Kerberos [KERBEROS] instead for requests that
+ would normally be sent with a bearer token, but servers SHOULD NOT
+ impose any such access barriers for resources that would normally
+ not require an access token.
+
+ The '...' ellipses indicate that more properties may be present.
+ Non-breaking examples that have been proposed so far, include a
+ "http://tools.ietf.org/html/rfc6750#section-2.3" property, set to
+ the string value "true" if the server supports passing the bearer
+ token in the URI query parameter as per section 2.3 of [BEARER],
+ instead of in the request header.
+
+
+de Jong [Page 10]
+
Internet-Draft remoteStorage December 2014
+
+
+
+ Another example is "http://tools.ietf.org/html/rfc7233" with a
+ string value of "GET" if Content-Range headers are supported for
+ GET requests as per [RANGE], "PUT" if they are supported for PUT
+ requests, and "GET,PUT" if supported for both.
+
+ Both these proposals are non-breaking extensions, since the client
+ will have a way to work around it if these features are not present
+ (e.g. retrieve the protected resource asynchronously in the first
+ case, or request the entire resource in the second case).
+
+ A "http://remotestorage.io/spec/web-authoring" property has been
+ proposed with a string value of the fully qualified domain name to
+ which web authoring content is published if the server supports web
+ authoring as per [AUTHORING]. Note that this extension is a breaking
+ extension in the sense that it divides users into "haves", whose
+ remoteStorage accounts allow them to author web content, and
+ "have-nots", whose remoteStorage account does not support this
+ functionality.
+
+ The server MAY expire bearer tokens, and MAY require the user to
+ register applications as OAuth clients before first use; if no
+ client registration is required, then the server MAY ignore the
+ client_id parameter in favor of relying on the redirect_uri
+ parameter for client identification.
+
+11. Storage-first bearer token issuance
+
+ The provider MAY also present a dashboard to the user, where they
+ have some way to add open web app manifests [MANIFEST]. Adding a
+ manifest to the dashboard is considered equivalent to clicking
+ 'accept' in the dialog of the application-first flow. Removing one
+ is considered equivalent to revoking its access token.
+
+ As an equivalent to OAuth's 'scope' parameter, a 'datastores-access'
+ field SHOULD be present in the root of such an application manifest
+ document, with entries <module> -> '{"access": "readonly"}' for
+ <level> 'r' or '{"access": "readwrite"}' for <level> 'rw', as
+ prescribed in [DATASTORE].
+
+ When the user gestures they want to use a certain application whose
+ manifest is present on the dashboard, the dashboard SHOULD redirect
+ to the application or open it in a new window. To mimic coming back
+
+
+de Jong [Page 11]
+
Internet-Draft remoteStorage December 2014
+
+
+ from the OAuth dialog, it MAY add 'access_token' and 'scope'
+ fields to the URL fragment.
+
+ Regardless of whether 'access_token' and 'scope' are specified, it
+ SHOULD add a 'remotestorage' field to the URL fragment, with a
+ value of the form <account> '@' <host>. When the application detects
+ this parameter, it SHOULD resolve the WebFinger record for <account>
+ at <host> and extract the <storage_root> and <storage_api>
+ information.
+
+ If no access_token was given, then the application SHOULD also
+ extract the <auth_endpoint> information from WebFinger, and continue
+ as per application-first bearer token issuance.
+
+ Note that whereas a remoteStorage server SHOULD offer support for
+ the application-first flow with WebFinger and OAuth, it MAY choose
+ not to support the storage-first flow, provided that users will
+ easily remember their <account> '@' <host> WebFinger address at that
+ provider. Applications SHOULD, however, support both flows, which
+ means checking the URL for a 'remotestorage' parameter, but giving
+ the user a way to specify the WebFinger address if there is none.
+
+ If a server provides an application manifest dashboard, then it
+ SHOULD merge the list of applications there with the list of
+ issued access tokens as specified by OAuth into one list. Also,
+ the interface for revoking an access token as specified by OAuth
+ SHOULD coincide with removing an application from the dashboard.
+
+ Servers MAY also provide a way to create access tokens directly from
+ their user interface. Such functionality would be aimed mainly at
+ developers, to manually copy and paste a token into a script or
+ debug tool, thus bypassing the need for an OAuth dance. Clients
+ SHOULD NOT rely on this in production.
+
+12. Example wire transcripts
+
+ The following examples are not normative ("\" indicates a line was
+ wrapped).
+
+12.1. WebFinger
+
+ In application-first, an in-browser application might issue the
+ following request, using XMLHttpRequest and CORS:
+
+
+de Jong [Page 12]
Internet-Draft remoteStorage December 2014
+
+
+
+ GET /oauth/michiel?redirect_uri=https%3A%2F%2Fdrinks-unhosted.5\
+apps.com%2F&scope=myfavoritedrinks%3Arw&client_id=https%3A%2F%2Fdrinks-\
+unhosted.5apps.com&response_type=token HTTP/1.1
+ Host: 3pp.io
+
+ The server's response might look like this (truncated for brevity):
+
+ HTTP/1.1 200 OK
+
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <title>Allow access?</title>
+ ...
+
+12.3. OAuth dialog form submission
+
+ When the user submits the form, the request would look something
+ like this:
+
+ POST /oauth HTTP/1.1
+ Host: 3pp.io:4439
+ Origin: https://3pp.io:4439
+ Content-Type: application/x-www-form-urlencoded
+ Referer: https://3pp.io:4439/oauth/michiel?redirect_uri=https%3\
+A%2F%2Fdrinks-unhosted.5apps.com%2F&scope=myfavoritedrinks%3Arw&client_\
+id=https%3A%2F%2Fdrinks-unhosted.5apps.com&response_type=token
+
+ client_id=https%3A%2F%2Fdrinks-unhosted.5apps.com&redirect_uri=\
+https%3A%2F%2Fdrinks-unhosted.5apps.com%2F&response_type=token&scope=my\
+favoritedrinks%3Arw&state=&username=michiel&password=something&allow=Al\
+low
+
+ To which the server could respond with a 302 redirect, back to the
+ origin of the requesting application:
+
+ HTTP/1.1 302 Found
+ Location:https://drinks-unhosted.5apps.com/#access_token=j2YnGt\
+XjzzzHNjkd1CJxoQubA1o%3D&token_type=bearer&state=
+
+12.4. OPTIONS preflightde Jong [Page 14]
+
Internet-Draft remoteStorage December 2014
+
+
+ When an in-browser application makes a cross-origin request which
+ may affect the server-state, the browser will make a preflight
+ request first, with the OPTIONS verb, for instance:
+
+ OPTIONS /storage/michiel/myfavoritedrinks/ HTTP/1.1
+ Host: 3pp.io:4439
+ Access-Control-Request-Method: GET
+ Origin: https://drinks-unhosted.5apps.com
+ Access-Control-Request-Headers: Authorization
+ Referer: https://drinks-unhosted.5apps.com/
+
+ To which the server can for instance respond:
+
+ HTTP/1.1 200 OK
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ Access-Control-Allow-Methods: GET, PUT, DELETE
+ Access-Control-Allow-Headers: Authorization, Content-Length, Co\
+ntent-Type, Origin, X-Requested-With, If-Match, If-None-Match
+
+12.5. Initial PUT
+
+ An initial PUT may contain an 'If-None-Match: *' header, like this:
+
+ PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Content-Length: 91
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Content-Type: application/json; charset=UTF-8
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-None-Match: *
+
+ {"name":"test","@context":"http://remotestorage.io/spec/modules\
+/myfavoritedrinks/drink"}
+
+ And the server may respond with either a 201 Created or a 200 OK
+ status:
+
+ HTTP/1.1 201 Created
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694045000"
+
+12.6. Subsequent PUTde Jong [Page 15]
+
Internet-Draft remoteStorage December 2014
+
+
+
+ A subsequent PUT may contain an 'If-Match' header referring to the
+ ETag previously returned, like this:
+
+ PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Content-Length: 91
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Content-Type: application/json; charset=UTF-8
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-Match: "1382694045000"
+
+ {"name":"test", "updated":true, "@context":"http://remotestorag\
+e.io/spec/modules/myfavoritedrinks/drink"}
+
+ And the server may respond with a 412 Conflict or a 200 OK status:
+
+ HTTP/1.1 200 OK
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694048000"
+
+12.7. GET
+
+ A GET request would also include the bearer token, and optionally
+ an If-None-Match header:
+
+ GET /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-None-Match: "1382694045000", "1382694048000"
+
+ And the server may respond with a 304 Not Modified status:
+
+ HTTP/1.1 304 Not Modified
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694048000"
+
+ Or a 200 OK status, plus a response body:
+
+ HTTP/1.1 200 OK
+
+
+de Jong [Page 16]
+
Internet-Draft remoteStorage December 2014
+
+
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ Content-Type: application/json; charset=UTF-8
+ Content-Length: 106
+ ETag: "1382694048000"
+ Expires: 0
+
+ {"name":"test", "updated":true, "@context":"http://remotestora\
+ge.io/spec/modules/myfavoritedrinks/drink"}
+
+ If the GET URL would have been "/storage/michiel/myfavoritedrinks/",
+ a 200 OK response would have a folder description as the response
+ body:
+
+ HTTP/1.1 200 OK
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ Content-Type: application/ld+json
+ Content-Length: 171
+ ETag: "1382694048000"
+ Expires: 0
+
+ {"@context":"http://remotestorage.io/spec/folder-version","ite\
+ms":{"test":{"ETag":"1382694048000","Content-Type":"application/json; \
+charset=UTF-8","Content-Length":106}}}
+
+ If the GET URL would have been a non-existing document like
+ "/storage/michiel/myfavoritedrinks/x", the response would have a 404
+ Not Found status, and no ETag header:
+
+ HTTP/1.1 404 Not Found
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com12.8. DELETE
+
+ A DELETE request may look like this:
+
+ DELETE /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Content-Type: application/json; charset=UTF-8
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-Match: "1382694045000"
+
+
+
+de Jong [Page 17]
+
Internet-Draft remoteStorage December 2014
+
+
+ And the server may respond with a 412 Conflict or a 200 OK status:
+
+ HTTP/1.1 412 Conflict
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694048000"
+
+13. Distributed versioning
+
+ This section is non-normative, and is intended to explain some of
+ the design choices concerning ETags and folder listings. At the
+ same time it will hopefully help readers who intend to develop an
+ application that uses remoteStorage as its per-user data storage.
+ When multiple clients have read/write access to the same document,
+ versioning conflicts may occur. For instance, client A may make
+ a PUT request that changes the document from version 1 to version
+ 2, after which client B may make a PUT request attempting to change
+ the same document from version 1 to version 3.
+
+ In this case, client B can add an 'If-Match: "1"' header, which
+ would trigger a 412 Conflict response code, since the current
+ version ("2") does not match the version required as a condition by
+ the header If-Match header ("1").
+
+ Client B is now aware of the conflict, and may consult the user,
+ saying the update to version 3 failed. The user may then choose,
+ through the user interface of client B, whether version 2 or
+ version 3 should be kept, or maybe the document should be reverted
+ on the server to version 1, or a merged version 4 is needed. Client
+ B may then make a request that puts the document to the version the
+ user wishes; this time setting an 'If-Match: "2"' header instead.
+
+ Both client A and client B would periodically poll the root
+ folder of each scope they have access to, to see if the version
+ of the root folder changed. If it did, then one of the versions
+ listed in there will necessarily have changed, and the client can
+ make a GET request to that child folder or document, to obtain
+ its latest version.
+
+ Because an update in a document will result in a version change of
+ its containing folder, and that change will propagate all the way
+ to the root folder, it is not necessary to poll each document for
+ changes individually.
+
+
+
+de Jong [Page 18]
+
Internet-Draft remoteStorage December 2014
+
+
+ As an example, the root folder may contain 10 directories,
+ each of which contain 10 directories, which each contain 10
+ documents, so their paths would be for instance '/0/0/1', '/0/0/2',
+ etcetera. Then one GET request to the root folder '/' will be
+ enough to know if any of these 1000 documents has changed.
+
+ Say document '/7/9/2' has changed; then the GET request to '/' will
+ come back with a different ETag, and entry '7/' will have a
+ different value in its JSON content. The client could then request
+ '/7/', '/7/9/', and '/7/9/2' to narrow down the one document that
+ caused the root folder's ETag to change.
+
+ Note that the remoteStorage server does not get involved in the
+ conflict resolution. It keeps the canonical current version at all
+ times, and allows clients to make conditional GET and PUT requests,
+ but it is up to whichever client discovers a given version
+ conflict, to resolve it.
+
+14. Security Considerations
+
+ To prevent man-in-the-middle attacks, the use of https instead of
+ http is important for both the interface itself and all end-points
+ involved in WebFinger, OAuth, and (if present) the storage-first
+ application launch dashboard.
+
+ A malicious party could link to an application, but specifying a
+ remoteStorage account address that it controls, thus tricking the
+ user into using a trusted application to send sensitive data to the
+ wrong remoteStorage server. To mitigate this, applications SHOULD
+ clearly display to which remoteStorage server they are sending the
+ user's data.
+
+ Applications could request scopes that the user did not intend to
+ give access to. The user SHOULD always be prompted to carefully
+ review which scopes an application is requesting.
+
+ An application may upload malicious html pages and then trick the
+ user into visiting them, or upload malicious client-side scripts,
+ that take advantage of being hosted on the user's domain name. The
+ origin on which the remoteStorage server has its interface SHOULD
+ therefore NOT be used for anything else, and the user SHOULD be
+ warned not to visit any web pages on that origin. In particular, the
+ OAuth dialog and launch dashboard or token revokation interface
+
+
+de Jong [Page 19]
+
Internet-Draft remoteStorage December 2014
+
+
+ SHOULD be on a different origin than the remoteStorage interface.
+
+ Where the use of bearer tokens is impractical, a user may choose to
+ store documents on hard-to-guess URLs whose path after
+ <storage_root> starts with '/public/', while sharing this URL only
+ with the intended audience. That way, only parties who know the
+ document's hard-to-guess URL, can access it. The server SHOULD
+ therefore make an effort to detect and stop brute-force attacks that
+ attempt to guess the location of such documents.
+
+ The server SHOULD also detect and stop denial-of-service attacks
+ that aim to overwhelm its interface with too much traffic.
+
+15. IANA Considerations
+
+ This document registers the 'remotestorage' link relation, as well
+ as the following WebFinger properties:
+ * "http://remotestorage.io/spec/version"
+ * "http://tools.ietf.org/html/rfc6749#section-4.2"
+ * "http://tools.ietf.org/html/rfc6750#section-2.3"
+ * "http://tools.ietf.org/html/rfc7233"
+ * "http://remotestorage.io/spec/web-authoring"
+
+16. Acknowledgements
+
+ The authors would like to thank everybody who contributed to the
+ development of this protocol, including Kenny Bentley, Javier Diaz,
+ Daniel Groeber, Bjarni Runar, Jan Wildeboer, Charles Schultz, Peter
+ Svensson, Valer Mischenko, Michiel Leenaars, Jan-Christoph
+ Borchardt, Garret Alfert, Sebastian Kippe, Max Wiehle, Melvin
+ Carvalho, Martin Stadler, Geoffroy Couprie, Niklas Cathor, Marco
+ Stahl, James Coglan, Ken Eucker, Daniel Brolund, elf Pavlik, Nick
+ Jennings, Markus Sabadello, Steven te Brinke, Matthias Treydte,
+ Rick van Rein, Mark Nottingham, Julian Reschke, and Markus
+ Lanthaler, among many others.
+
+17. References17.1. Normative References
+
+ [WORDS]
+ Bradner, S., "Key words for use in RFCs to Indicate Requirement
+ Levels", BCP 14, RFC 2119, March 1997.
+
+
+de Jong [Page 20]
+
Internet-Draft remoteStorage December 2014
+
+
+
+ [IRI]
+ Duerst, M., "Internationalized Resource Identifiers (IRIs)",
+ RFC 3987, January 2005.
+
+ [WEBFINGER]
+ Jones, P., Salguerio, G., Jones, M, and Smarr, J.,
+ "WebFinger", RFC7033, September 2013.
+
+ [OAUTH]
+ "Section 4.2: Implicit Grant", in: Hardt, D. (ed), "The OAuth
+ 2.0 Authorization Framework", RFC6749, October 2012.
+
+17.2. Informative References
+
+ [HTTPS]
+ Rescorla, E., "HTTP Over TLS", RFC2818, May 2000.
+
+ [HTTP]
+ Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1):
+ Semantics and Content", RFC7231, June 2014.
+
+ [COND]
+ Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1):
+ Conditional Requests", RFC7232, June 2014.
+
+ [RANGE]
+ Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1):
+ Conditional Requests", RFC7233, June 2014.
+
+ [SPDY]
+ Mark Belshe, Roberto Peon, "SPDY Protocol - Draft 3.1", http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1,
+ September 2013.
+
+ [JSON-LD]
+ M. Sporny, G. Kellogg, M. Lanthaler, "JSON-LD 1.0", W3C
+ Proposed Recommendation,
+ http://www.w3.org/TR/2014/REC-json-ld-20140116/, January 2014.
+
+ [CORS]
+ van Kesteren, Anne (ed), "Cross-Origin Resource Sharing --
+ W3C Candidate Recommendation 29 January 2013",
+
+
+de Jong [Page 21]
+
Internet-Draft remoteStorage December 2014http://www.w3.org/TR/cors/, January 2013.
+
+ [MANIFEST]
+ Mozilla Developer Network (ed), "App manifest -- Revision
+ 330541", https://developer.mozilla.org/en-
+ US/Apps/Build/Manifest$revision/566677, April 2014.
+
+ [DATASTORE]
+ "WebAPI/DataStore", MozillaWiki, retrieved May 2014.
+ https://wiki.mozilla.org/WebAPI/DataStore#Manifest
+
+ [KERBEROS]
+ C. Neuman et al., "The Kerberos Network Authentication Service
+ (V5)", RFC4120, July 2005.
+
+ [BEARER]
+ M. Jones, D. Hardt, "The OAuth 2.0 Authorization Framework:
+ Bearer Token Usage", RFC6750, October 2012.
+
+ []
+ "Using remoteStorage for web authoring", reSite wiki, retrieved
+ September 2014. https://github.com/michielbdejong/resite/wiki
+ /Using-remoteStorage-for-web-authoring
+
+18. Authors' addresses
+
+ Michiel B. de Jong
+ IndieHosters
+
+ Email: michiel@michielbdejong.com
+
+
+ F. Kooman
+ (independent)
+
+ Email: fkooman@tuxed.net
+
+
+
+
+
+
+
+
+
+de Jong [Page 22]
+
+
INTERNET DRAFT Michiel B. de Jong
+Document: draft-dejong-remotestorage-04 IndieHosters
+ F. Kooman
+Intended Status: Proposed Standard (independent)
+Expires: 18 June 2015 15 December 2014
+
+
+ remoteStorage
+
+Abstract
+
+ This draft describes a protocol by which client-side applications,
+ running inside a web browser, can communicate with a data storage
+ server that is hosted on a different domain name. This way, the
+ provider of a web application need not also play the role of data
+ storage provider. The protocol supports storing, retrieving, and
+ removing individual documents, as well as listing the contents of an
+ individual folder, and access control is based on bearer tokens.
+
+Status of this Memo
+
+ This Internet-Draft is submitted in full conformance with the
+ provisions of BCP 78 and BCP 79.
+
+ Internet-Drafts are working documents of the Internet Engineering
+ Task Force (IETF). Note that other groups may also distribute
+ working documents as Internet-Drafts. The list of current Internet-
+ Drafts is at http://datatracker.ietf.org/drafts/current/.
+
+ Internet-Drafts are draft documents valid for a maximum of six months
+ and may be updated, replaced, or obsoleted by other documents at any
+ time. It is inappropriate to use Internet-Drafts as reference
+ material or to cite them other than as "work in progress."
+
+ This Internet-Draft will expire on 15 December 2014.
+
+Copyright Notice
+
+ Copyright (c) 2014 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+de Jong [Page 1]
+
+Internet-Draft remoteStorage December 2014
+
+
+Table of Contents
+
+ 1. Introduction...................................................2
+ 2. Terminology....................................................3
+ 3. Storage model..................................................3
+ 4. Requests.......................................................4
+ 5. Response codes.................................................7
+ 6. Versioning.....................................................7
+ 7. CORS headers...................................................8
+ 8. Session description............................................8
+ 9. Bearer tokens and access control...............................9
+ 10. Application-first bearer token issuance.......................10
+ 11. Storage-first bearer token issuance...........................11
+ 12. Example wire transcripts......................................12
+ 12.1. WebFinger................................................12
+ 12.2. OAuth dialog form........................................13
+ 12.3. OAuth dialog form submission.............................14
+ 12.4. OPTIONS preflight........................................15
+ 12.5. Initial PUT..............................................15
+ 12.6. Subsequent PUT...........................................16
+ 12.7. GET......................................................16
+ 12.8. DELETE...................................................17
+ 13. Distributed versioning........................................17
+ 14. Security Considerations.......................................19
+ 15. IANA Considerations...........................................20
+ 16. Acknowledgments...............................................20
+ 17. References....................................................21
+ 17.1. Normative References.....................................21
+ 17.2. Informative References...................................21
+ 18. Authors' addresses............................................22
+
+
+1. Introduction
+
+ Many services for data storage are available over the internet. This
+ specification describes a vendor-independent interface for such
+ services. It is based on https, CORS and bearer tokens. The
+ metaphor for addressing data on the storage is that of folders
+ containing documents and subfolders. The actions the interface
+ exposes are:
+
+ * GET a folder: retrieve the names and current versions of the
+ documents and subfolders currently contained by the folder
+
+
+de Jong [Page 2]
+
+Internet-Draft remoteStorage December 2014
+
+
+ * GET a document: retrieve its content type, current version,
+ and contents
+
+ * PUT a document: store a new version, its content type, and
+ contents, conditional on the current version
+
+ * DELETE a document: remove it from the storage, conditional on
+ the current version
+
+ * HEAD a folder or document: like GET, but omitting the response
+ body
+
+ The exact details of these four actions are described in this
+ specification.
+
+2. Terminology
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in RFC 2119 [WORDS].
+
+ "SHOULD" and "SHOULD NOT" are appropriate when valid exceptions to a
+ general requirement are known to exist or appear to exist, and it is
+ infeasible or impractical to enumerate all of them. However, they
+ should not be interpreted as permitting implementors to fail to
+ implement the general requirement when such failure would result in
+ interoperability failure.
+
+3. Storage model
+
+ The server stores data in nodes that form a tree structure.
+ Internal nodes are called 'folders' and leaf nodes are called
+ 'documents'. For a folder, the server stores references to nodes
+ contained in the folder, and it should be able to produce a list of
+ them, with for each contained item:
+
+ * item name
+ * item type (folder or document)
+ * current version
+ * content type
+ * content length
+
+ For a document, the server stores, and should be able to produce:
+
+
+de Jong [Page 3]
+
+Internet-Draft remoteStorage December 2014
+
+
+
+ * current version
+ * content type
+ * content length
+ * content
+
+4. Requests
+
+ Client-to-server requests SHOULD be made over https [HTTPS], and
+ servers MUST comply with HTTP/1.1 [HTTP]. Specifically, they
+ MUST support chunked transfer coding on PUT requests. Servers MAY
+ also offer an optional switch from https to SPDY [SPDY].
+
+ A request is considered successful if the HTTP response code is in
+ the 2xx range (e.g. 200 OK, 201 Created), and unsuccessful if an
+ error occurred or a condition was not met (response code e.g. 404
+ Not Found, 304 Not Modified).
+
+ The root folder of the storage tree is represented by the following
+ URL:
+
+ URI_ENCODE( <storage_root> '/' )
+
+ Subsequently, if <parent_folder> is the URL of a folder, then the
+ URL of an item contained in it is:
+
+ URI_ENCODE( <parent_folder> <document_name> )
+
+ for a document, or:
+
+ URI_ENCODE( <parent_folder> <folder_name> '/' )
+
+ for a folder. Item names MAY contain all characters except '/' and
+ the null character, and MUST NOT have zero length.
+
+ A document description is a map containing one string-valued 'ETag'
+ field, one string-valued 'Content-Type' and one integer-valued
+ 'Content-Length' field. They represent the document's current
+ version, its content type, and its content length respectively. Note
+ that content length is measured in octets (bytes), not in
+ characters.
+
+ A folder description is a map containing a string-valued 'ETag'
+
+
+de Jong [Page 4]
+
+Internet-Draft remoteStorage December 2014
+
+
+ field, representing the folder's current version.
+
+ A successful GET request to a folder MUST be responded to with a
+ JSON-LD [JSON-LD] document (content type 'application/ld+json'),
+ containing as its 'items' field a map in which contained documents
+ appear as entries <item_name> to a document description, and
+ contained non-empty folders appear as entries <item_name> '/' to a
+ folder description. It MUST also contain an '@context' field with
+ the value 'http://remotestorage.io/spec/folder-description'. For
+ instance:
+
+ {
+ "@context": "http://remotestorage.io/spec/folder-description",
+ "items": {
+ "abc": {
+ "ETag": "DEADBEEFDEADBEEFDEADBEEF",
+ "Content-Type": "image/jpeg",
+ "Content-Length": 82352
+ },
+ "def/": {
+ "ETag": "1337ABCD1337ABCD1337ABCD"
+ }
+ }
+ }
+
+ All folders are treated as existing, and therefore GET requests to
+ untouched folders SHOULD be responded to with a folder description
+ with no items (the items field set to '{}'). However, an empty
+ folder MUST NOT be listed as an item in its parent folder.
+
+ Also, since folders exist automatically, PUT and DELETE requests
+ only need to be made to documents, and never to folders. A document
+ PUT will make all ancestor folders along its path become non-empty;
+ deleting the last document from a subtree will make that whole
+ subtree become empty. Folders will therefore show up in their parent
+ folder descriptions if and only if their subtree contains at least
+ one document.
+
+ A successful GET request to a document SHOULD be responded to with
+ the full document contents in the body, the document's content type
+ in a 'Content-Type' header, its content length in octets (not in
+ characters) in a 'Content-Length' header, and the document's current
+ version as a strong ETag in an 'ETag' header.
+
+
+de Jong [Page 5]
+
+Internet-Draft remoteStorage December 2014
+
+
+
+ Note that the use of strong ETags prohibits changing the response
+ body based on request headers; in particular, the server will not be
+ able to serve the same document uncompressed to some clients and
+ gzipped when requested by the client, since the two bodies would not
+ be identical byte-for-byte.
+
+ Servers MAY support Content-Range headers [RANGE] on GET requests,
+ but whether or not they do SHOULD be announced through the <ranges>
+ variable mentioned below in section 10.
+
+ A successful PUT request to a document MUST result in:
+
+ * the request body being stored as the document's new content,
+ * parent and further ancestor folders being silently created as
+ necessary, with the document (name and version) being added to
+ its parent folder, and each folder added to its subsequent
+ parent,
+ * the value of its Content-Type header being stored as the
+ document's new content type,
+ * its version being updated, as well as that of its parent folder
+ and further ancestor folders, using a strong validator [HTTP,
+ section 7.2].
+
+ The response MUST contain a strong ETag header, with the document's
+ new version (for instance a hash of its contents) as its value.
+
+ A successful DELETE request to a document MUST result in:
+
+ * the deletion of that document from the storage, and from its
+ parent folder,
+ * silent deletion of the parent folder if it is left empty by
+ this, and so on for further ancestor folders,
+ * the version of its parent folder being updated, as well as that
+ of further ancestor folders.
+
+ A successful OPTIONS request SHOULD be responded to as described in
+ the CORS section below.
+
+ A successful HEAD request SHOULD be responded to like to the
+ equivalent GET request, but omitting the response body.
+
+
+
+
+de Jong [Page 6]
+
+Internet-Draft remoteStorage December 2014
+
+
+5. Response codes
+
+ Response codes SHOULD be given as defined by [HTTP, section 6] and
+ [BEARER, section 3.1]. The following is a non-normative checklist
+ of status codes that are likely to occur in practice:
+
+ * 500 if an internal server error occurs,
+ * 429 if the client makes too frequent requests or is suspected
+ of malicious activity,
+ * 414 if the request URI is too long,
+ * 416 if Range requests are supported by the server and the Range
+ request can not be satisfied,
+ * 401 for all requests that don't have a bearer token with
+ sufficient permissions,
+ * 404 for all DELETE and GET requests to documents that do not
+ exist on the storage,
+ * 304 for a conditional GET request whose pre-condition
+ fails (see "Versioning" below),
+ * 409 for a PUT request where any folder name in the path
+ clashes with an existing document's name at the same
+ level, or where the document name coincides with an
+ existing folder's name at the same level.
+ * 412 for a conditional PUT or DELETE request whose pre-condition
+ fails (see "Versioning" below),
+ * 507 in case the account is over its storage quota,
+ * 4xx for all malformed requests (e.g. foreign characters in the
+ path), as well as for all PUT and DELETE requests to
+ folders,
+ * 2xx for all successful requests.
+
+ Clients SHOULD also handle the case where a response takes too long
+ to arrive, or where no response is received at all.
+
+6. Versioning
+
+ All successful requests MUST return an 'ETag' header [HTTP] with, in
+ the case of GET, the current version, in the case of PUT, the new
+ version, and in case of DELETE, the version that was deleted. All
+ successful GET requests MUST return an 'Expires: 0' header. PUT and
+ DELETE requests MAY have an 'If-Match' request header [COND], and
+ MUST fail with a 412 response code if that doesn't match the
+ document's current version.
+
+
+
+de Jong [Page 7]
+
+Internet-Draft remoteStorage December 2014
+
+
+ GET requests MAY have a comma-separated list of revisions in an
+ 'If-None-Match' header [COND], and SHOULD be responded to with a 304
+ response if that list includes the document or folder's current
+ version. A PUT request MAY have an 'If-None-Match: *' header [COND],
+ in which case it MUST fail with a 412 response code if the document
+ already exists.
+
+ In all 'ETag', 'If-Match' and 'If-None-Match' headers, revision
+ strings should appear inside double quotes (").
+
+ A provider MAY offer version rollback functionality to its users,
+ but this specification does not define the user interface for that.
+
+7. CORS headers
+
+ All responses MUST carry CORS headers [CORS]. The server MUST also
+ reply to OPTIONS requests as per CORS. For GET requests, a wildcard
+ origin MAY be returned, but for PUT and DELETE requests, the
+ response MUST echo back the Origin header sent by the client.
+
+8. Session description
+
+ The information that a client needs to receive in order to be able
+ to connect to a server SHOULD reach the client as described in the
+ 'bearer token issuance' sections below. It consists of:
+
+ * <storage_root>, consisting of 'https://' followed by a server
+ host, and optionally a server port and a path prefix as per
+ [IRI]. Examples:
+ * 'https://example.com' (host only)
+ * 'https://example.com:8080' (host and port)
+ * 'https://example.com/path/to/storage' (host, port and
+ path prefix; note there is no trailing slash)
+ * <access_token> as per [OAUTH]. The token SHOULD be hard to
+ guess and SHOULD NOT be reused from one client to another. It
+ can however be reused in subsequent interactions with the same
+ client, as long as that client is still trusted. Example:
+ * 'ofb24f1ac3973e70j6vts19qr9v2eei'
+ * <storage_api>, always 'draft-dejong-remotestorage-04' for this
+ alternative version of the specification.
+
+ The client can make its requests using https with CORS and bearer
+ tokens, to the URL that is the concatenation of <storage_root> with
+
+
+de Jong [Page 8]
+
+Internet-Draft remoteStorage December 2014
+
+
+ '/' plus one or more <folder> '/' strings indicating a path in the
+ folder tree, followed by zero or one <document> strings, indicating
+ a document. For example, if <storage_root> is
+ "https://storage.example.com/bob", then to retrieve the folder
+ contents of the /public/documents/ folder, or to retrieve a
+ 'draft.txt' document from that folder, the client would make
+ requests to, respectively:
+
+ * https://storage.example.com/bob/public/documents/
+ * https://storage.example.com/bob/public/documents/draft.txt
+
+9. Bearer tokens and access control
+
+ A bearer token represents one or more access scopes. These access
+ scopes are represented as strings of the form <module> <level>,
+ where the <module> string SHOULD be lower-case alphanumerical, other
+ than the reserved word 'public', and <level> can be ':r' or ':rw'.
+ The access the bearer token gives is the sum of its access scopes,
+ with each access scope representing the following permissions:
+
+ '*:rw') any request,
+
+ '*:r') any GET or HEAD request,
+
+ <module> ':rw') any requests to paths that start with
+ '/' <module> '/' or '/public/' <module> '/',
+
+ <module> ':r') any GET or HEAD requests to paths that start with
+ '/' <module> '/' or '/public/' <module> '/',
+
+ As a special exceptions, GET requests to a document (but not a
+ folder) whose path starts with '/public/' are always allowed. They,
+ as well as OPTIONS requests, can be made without a bearer token.
+ Unless [KERBEROS] is used (see section 10 below), all other requests
+ SHOULD present a bearer token with sufficient access scope, using a
+ header of the following form (no double quotes here):
+
+ Authorization: Bearer <access_token>
+
+ In addition, providing the access token via a HTTP query parameter
+ for GET requests MAY be supported by the server, although its use
+ is not recommended, due to its security deficiencies; see [BEARER,
+ section 2.3].
+
+
+de Jong [Page 9]
+
+Internet-Draft remoteStorage December 2014
+
+
+
+10. Application-first bearer token issuance
+
+ To make a remoteStorage server available as 'the remoteStorage of
+ <account> at <host>', exactly one link of the following format
+ SHOULD be added to the WebFinger record [WEBFINGER] of <account> at
+ <host>:
+
+ {
+ "href": <storage_root>,
+ "rel": "remotestorage",
+ "properties": {
+ "http://remotestorage.io/spec/version": <storage_api>,
+ "http://tools.ietf.org/html/rfc6749#section-4.2": <auth-dialog>,
+ ... : ... ,
+ }
+ }
+
+ Here <storage_root> and <storage_api> are as per "Session
+ description" above, and <auth-dialog> SHOULD be either null or a
+ URL where an OAuth 2.0 implicit-grant flow dialog [OAUTH] is
+ presented.
+
+ If <auth-dialog> is a URL, the user can supply their credentials
+ for accessing the account (how, is out of scope), and allow or
+ reject a request by the connecting application to obtain a bearer
+ token for a certain list of access scopes. Note that an account
+ will often belong to just one human user, but may also belong to a
+ group of multiple users (the remoteStorage of <group> at <host>).
+
+ If <auth-dialog> is null, the client will not have a way to obtain
+ an access token, and SHOULD send all requests without Authorization
+ header, and rely on Kerberos [KERBEROS] instead for requests that
+ would normally be sent with a bearer token, but servers SHOULD NOT
+ impose any such access barriers for resources that would normally
+ not require an access token.
+
+ The '...' ellipses indicate that more properties may be present.
+ Non-breaking examples that have been proposed so far, include a
+ "http://tools.ietf.org/html/rfc6750#section-2.3" property, set to
+ the string value "true" if the server supports passing the bearer
+ token in the URI query parameter as per section 2.3 of [BEARER],
+ instead of in the request header.
+
+
+de Jong [Page 10]
+
+Internet-Draft remoteStorage December 2014
+
+
+
+ Another example is "http://tools.ietf.org/html/rfc7233" with a
+ string value of "GET" if Content-Range headers are supported for
+ GET requests as per [RANGE], "PUT" if they are supported for PUT
+ requests, and "GET,PUT" if supported for both.
+
+ Both these proposals are non-breaking extensions, since the client
+ will have a way to work around it if these features are not present
+ (e.g. retrieve the protected resource asynchronously in the first
+ case, or request the entire resource in the second case).
+
+ A "http://remotestorage.io/spec/web-authoring" property has been
+ proposed with a string value of the fully qualified domain name to
+ which web authoring content is published if the server supports web
+ authoring as per [AUTHORING]. Note that this extension is a breaking
+ extension in the sense that it divides users into "haves", whose
+ remoteStorage accounts allow them to author web content, and
+ "have-nots", whose remoteStorage account does not support this
+ functionality.
+
+ The server MAY expire bearer tokens, and MAY require the user to
+ register applications as OAuth clients before first use; if no
+ client registration is required, then the server MAY ignore the
+ client_id parameter in favor of relying on the redirect_uri
+ parameter for client identification.
+
+11. Storage-first bearer token issuance
+
+ The provider MAY also present a dashboard to the user, where they
+ have some way to add open web app manifests [MANIFEST]. Adding a
+ manifest to the dashboard is considered equivalent to clicking
+ 'accept' in the dialog of the application-first flow. Removing one
+ is considered equivalent to revoking its access token.
+
+ As an equivalent to OAuth's 'scope' parameter, a 'datastores-access'
+ field SHOULD be present in the root of such an application manifest
+ document, with entries <module> -> '{"access": "readonly"}' for
+ <level> 'r' or '{"access": "readwrite"}' for <level> 'rw', as
+ prescribed in [DATASTORE].
+
+ When the user gestures they want to use a certain application whose
+ manifest is present on the dashboard, the dashboard SHOULD redirect
+ to the application or open it in a new window. To mimic coming back
+
+
+de Jong [Page 11]
+
+Internet-Draft remoteStorage December 2014
+
+
+ from the OAuth dialog, it MAY add 'access_token' and 'scope'
+ fields to the URL fragment.
+
+ Regardless of whether 'access_token' and 'scope' are specified, it
+ SHOULD add a 'remotestorage' field to the URL fragment, with a
+ value of the form <account> '@' <host>. When the application detects
+ this parameter, it SHOULD resolve the WebFinger record for <account>
+ at <host> and extract the <storage_root> and <storage_api>
+ information.
+
+ If no access_token was given, then the application SHOULD also
+ extract the <auth_endpoint> information from WebFinger, and continue
+ as per application-first bearer token issuance.
+
+ Note that whereas a remoteStorage server SHOULD offer support for
+ the application-first flow with WebFinger and OAuth, it MAY choose
+ not to support the storage-first flow, provided that users will
+ easily remember their <account> '@' <host> WebFinger address at that
+ provider. Applications SHOULD, however, support both flows, which
+ means checking the URL for a 'remotestorage' parameter, but giving
+ the user a way to specify the WebFinger address if there is none.
+
+ If a server provides an application manifest dashboard, then it
+ SHOULD merge the list of applications there with the list of
+ issued access tokens as specified by OAuth into one list. Also,
+ the interface for revoking an access token as specified by OAuth
+ SHOULD coincide with removing an application from the dashboard.
+
+ Servers MAY also provide a way to create access tokens directly from
+ their user interface. Such functionality would be aimed mainly at
+ developers, to manually copy and paste a token into a script or
+ debug tool, thus bypassing the need for an OAuth dance. Clients
+ SHOULD NOT rely on this in production.
+
+12. Example wire transcripts
+
+ The following examples are not normative ("\" indicates a line was
+ wrapped).
+
+12.1. WebFinger
+
+ In application-first, an in-browser application might issue the
+ following request, using XMLHttpRequest and CORS:
+
+
+de Jong [Page 12]
+
+Internet-Draft remoteStorage December 2014
+
+
+
+ GET /oauth/michiel?redirect_uri=https%3A%2F%2Fdrinks-unhosted.5\
+apps.com%2F&scope=myfavoritedrinks%3Arw&client_id=https%3A%2F%2Fdrinks-\
+unhosted.5apps.com&response_type=token HTTP/1.1
+ Host: 3pp.io
+
+ The server's response might look like this (truncated for brevity):
+
+ HTTP/1.1 200 OK
+
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <title>Allow access?</title>
+ ...
+
+12.3. OAuth dialog form submission
+
+ When the user submits the form, the request would look something
+ like this:
+
+ POST /oauth HTTP/1.1
+ Host: 3pp.io:4439
+ Origin: https://3pp.io:4439
+ Content-Type: application/x-www-form-urlencoded
+ Referer: https://3pp.io:4439/oauth/michiel?redirect_uri=https%3\
+A%2F%2Fdrinks-unhosted.5apps.com%2F&scope=myfavoritedrinks%3Arw&client_\
+id=https%3A%2F%2Fdrinks-unhosted.5apps.com&response_type=token
+
+ client_id=https%3A%2F%2Fdrinks-unhosted.5apps.com&redirect_uri=\
+https%3A%2F%2Fdrinks-unhosted.5apps.com%2F&response_type=token&scope=my\
+favoritedrinks%3Arw&state=&username=michiel&password=something&allow=Al\
+low
+
+ To which the server could respond with a 302 redirect, back to the
+ origin of the requesting application:
+
+ HTTP/1.1 302 Found
+ Location:https://drinks-unhosted.5apps.com/#access_token=j2YnGt\
+XjzzzHNjkd1CJxoQubA1o%3D&token_type=bearer&state=
+
+12.4. OPTIONS preflight
+
+
+
+de Jong [Page 14]
+
+Internet-Draft remoteStorage December 2014
+
+
+ When an in-browser application makes a cross-origin request which
+ may affect the server-state, the browser will make a preflight
+ request first, with the OPTIONS verb, for instance:
+
+ OPTIONS /storage/michiel/myfavoritedrinks/ HTTP/1.1
+ Host: 3pp.io:4439
+ Access-Control-Request-Method: GET
+ Origin: https://drinks-unhosted.5apps.com
+ Access-Control-Request-Headers: Authorization
+ Referer: https://drinks-unhosted.5apps.com/
+
+ To which the server can for instance respond:
+
+ HTTP/1.1 200 OK
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ Access-Control-Allow-Methods: GET, PUT, DELETE
+ Access-Control-Allow-Headers: Authorization, Content-Length, Co\
+ntent-Type, Origin, X-Requested-With, If-Match, If-None-Match
+
+12.5. Initial PUT
+
+ An initial PUT may contain an 'If-None-Match: *' header, like this:
+
+ PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Content-Length: 91
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Content-Type: application/json; charset=UTF-8
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-None-Match: *
+
+ {"name":"test","@context":"http://remotestorage.io/spec/modules\
+/myfavoritedrinks/drink"}
+
+ And the server may respond with either a 201 Created or a 200 OK
+ status:
+
+ HTTP/1.1 201 Created
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694045000"
+
+12.6. Subsequent PUT
+
+
+de Jong [Page 15]
+
+Internet-Draft remoteStorage December 2014
+
+
+
+ A subsequent PUT may contain an 'If-Match' header referring to the
+ ETag previously returned, like this:
+
+ PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Content-Length: 91
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Content-Type: application/json; charset=UTF-8
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-Match: "1382694045000"
+
+ {"name":"test", "updated":true, "@context":"http://remotestorag\
+e.io/spec/modules/myfavoritedrinks/drink"}
+
+ And the server may respond with a 412 Conflict or a 200 OK status:
+
+ HTTP/1.1 200 OK
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694048000"
+
+12.7. GET
+
+ A GET request would also include the bearer token, and optionally
+ an If-None-Match header:
+
+ GET /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-None-Match: "1382694045000", "1382694048000"
+
+ And the server may respond with a 304 Not Modified status:
+
+ HTTP/1.1 304 Not Modified
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694048000"
+
+ Or a 200 OK status, plus a response body:
+
+ HTTP/1.1 200 OK
+
+
+de Jong [Page 16]
+
+Internet-Draft remoteStorage December 2014
+
+
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ Content-Type: application/json; charset=UTF-8
+ Content-Length: 106
+ ETag: "1382694048000"
+ Expires: 0
+
+ {"name":"test", "updated":true, "@context":"http://remotestora\
+ge.io/spec/modules/myfavoritedrinks/drink"}
+
+ If the GET URL would have been "/storage/michiel/myfavoritedrinks/",
+ a 200 OK response would have a folder description as the response
+ body:
+
+ HTTP/1.1 200 OK
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ Content-Type: application/ld+json
+ Content-Length: 171
+ ETag: "1382694048000"
+ Expires: 0
+
+ {"@context":"http://remotestorage.io/spec/folder-version","ite\
+ms":{"test":{"ETag":"1382694048000","Content-Type":"application/json; \
+charset=UTF-8","Content-Length":106}}}
+
+ If the GET URL would have been a non-existing document like
+ "/storage/michiel/myfavoritedrinks/x", the response would have a 404
+ Not Found status, and no ETag header:
+
+ HTTP/1.1 404 Not Found
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+
+12.8. DELETE
+
+ A DELETE request may look like this:
+
+ DELETE /storage/michiel/myfavoritedrinks/test HTTP/1.1
+ Host: 3pp.io:4439
+ Origin: https://drinks-unhosted.5apps.com
+ Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o=
+ Content-Type: application/json; charset=UTF-8
+ Referer: https://drinks-unhosted.5apps.com/?
+ If-Match: "1382694045000"
+
+
+
+de Jong [Page 17]
+
+Internet-Draft remoteStorage December 2014
+
+
+ And the server may respond with a 412 Conflict or a 200 OK status:
+
+ HTTP/1.1 412 Conflict
+ Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com
+ ETag: "1382694048000"
+
+13. Distributed versioning
+
+ This section is non-normative, and is intended to explain some of
+ the design choices concerning ETags and folder listings. At the
+ same time it will hopefully help readers who intend to develop an
+ application that uses remoteStorage as its per-user data storage.
+ When multiple clients have read/write access to the same document,
+ versioning conflicts may occur. For instance, client A may make
+ a PUT request that changes the document from version 1 to version
+ 2, after which client B may make a PUT request attempting to change
+ the same document from version 1 to version 3.
+
+ In this case, client B can add an 'If-Match: "1"' header, which
+ would trigger a 412 Conflict response code, since the current
+ version ("2") does not match the version required as a condition by
+ the header If-Match header ("1").
+
+ Client B is now aware of the conflict, and may consult the user,
+ saying the update to version 3 failed. The user may then choose,
+ through the user interface of client B, whether version 2 or
+ version 3 should be kept, or maybe the document should be reverted
+ on the server to version 1, or a merged version 4 is needed. Client
+ B may then make a request that puts the document to the version the
+ user wishes; this time setting an 'If-Match: "2"' header instead.
+
+ Both client A and client B would periodically poll the root
+ folder of each scope they have access to, to see if the version
+ of the root folder changed. If it did, then one of the versions
+ listed in there will necessarily have changed, and the client can
+ make a GET request to that child folder or document, to obtain
+ its latest version.
+
+ Because an update in a document will result in a version change of
+ its containing folder, and that change will propagate all the way
+ to the root folder, it is not necessary to poll each document for
+ changes individually.
+
+
+
+de Jong [Page 18]
+
+Internet-Draft remoteStorage December 2014
+
+
+ As an example, the root folder may contain 10 directories,
+ each of which contain 10 directories, which each contain 10
+ documents, so their paths would be for instance '/0/0/1', '/0/0/2',
+ etcetera. Then one GET request to the root folder '/' will be
+ enough to know if any of these 1000 documents has changed.
+
+ Say document '/7/9/2' has changed; then the GET request to '/' will
+ come back with a different ETag, and entry '7/' will have a
+ different value in its JSON content. The client could then request
+ '/7/', '/7/9/', and '/7/9/2' to narrow down the one document that
+ caused the root folder's ETag to change.
+
+ Note that the remoteStorage server does not get involved in the
+ conflict resolution. It keeps the canonical current version at all
+ times, and allows clients to make conditional GET and PUT requests,
+ but it is up to whichever client discovers a given version
+ conflict, to resolve it.
+
+14. Security Considerations
+
+ To prevent man-in-the-middle attacks, the use of https instead of
+ http is important for both the interface itself and all end-points
+ involved in WebFinger, OAuth, and (if present) the storage-first
+ application launch dashboard.
+
+ A malicious party could link to an application, but specifying a
+ remoteStorage account address that it controls, thus tricking the
+ user into using a trusted application to send sensitive data to the
+ wrong remoteStorage server. To mitigate this, applications SHOULD
+ clearly display to which remoteStorage server they are sending the
+ user's data.
+
+ Applications could request scopes that the user did not intend to
+ give access to. The user SHOULD always be prompted to carefully
+ review which scopes an application is requesting.
+
+ An application may upload malicious html pages and then trick the
+ user into visiting them, or upload malicious client-side scripts,
+ that take advantage of being hosted on the user's domain name. The
+ origin on which the remoteStorage server has its interface SHOULD
+ therefore NOT be used for anything else, and the user SHOULD be
+ warned not to visit any web pages on that origin. In particular, the
+ OAuth dialog and launch dashboard or token revokation interface
+
+
+de Jong [Page 19]
+
+Internet-Draft remoteStorage December 2014
+
+
+ SHOULD be on a different origin than the remoteStorage interface.
+
+ Where the use of bearer tokens is impractical, a user may choose to
+ store documents on hard-to-guess URLs whose path after
+ <storage_root> starts with '/public/', while sharing this URL only
+ with the intended audience. That way, only parties who know the
+ document's hard-to-guess URL, can access it. The server SHOULD
+ therefore make an effort to detect and stop brute-force attacks that
+ attempt to guess the location of such documents.
+
+ The server SHOULD also detect and stop denial-of-service attacks
+ that aim to overwhelm its interface with too much traffic.
+
+15. IANA Considerations
+
+ This document registers the 'remotestorage' link relation, as well
+ as the following WebFinger properties:
+ * "http://remotestorage.io/spec/version"
+ * "http://tools.ietf.org/html/rfc6749#section-4.2"
+ * "http://tools.ietf.org/html/rfc6750#section-2.3"
+ * "http://tools.ietf.org/html/rfc7233"
+ * "http://remotestorage.io/spec/web-authoring"
+
+16. Acknowledgements
+
+ The authors would like to thank everybody who contributed to the
+ development of this protocol, including Kenny Bentley, Javier Diaz,
+ Daniel Groeber, Bjarni Runar, Jan Wildeboer, Charles Schultz, Peter
+ Svensson, Valer Mischenko, Michiel Leenaars, Jan-Christoph
+ Borchardt, Garret Alfert, Sebastian Kippe, Max Wiehle, Melvin
+ Carvalho, Martin Stadler, Geoffroy Couprie, Niklas Cathor, Marco
+ Stahl, James Coglan, Ken Eucker, Daniel Brolund, elf Pavlik, Nick
+ Jennings, Markus Sabadello, Steven te Brinke, Matthias Treydte,
+ Rick van Rein, Mark Nottingham, Julian Reschke, and Markus
+ Lanthaler, among many others.
+
+17. References
+
+17.1. Normative References
+
+ [WORDS]
+ Bradner, S., "Key words for use in RFCs to Indicate Requirement
+ Levels", BCP 14, RFC 2119, March 1997.
+
+
+de Jong [Page 20]
+
+Internet-Draft remoteStorage December 2014
+
+
+
+ [IRI]
+ Duerst, M., "Internationalized Resource Identifiers (IRIs)",
+ RFC 3987, January 2005.
+
+ [WEBFINGER]
+ Jones, P., Salguerio, G., Jones, M, and Smarr, J.,
+ "WebFinger", RFC7033, September 2013.
+
+ [OAUTH]
+ "Section 4.2: Implicit Grant", in: Hardt, D. (ed), "The OAuth
+ 2.0 Authorization Framework", RFC6749, October 2012.
+
+17.2. Informative References
+
+ [HTTPS]
+ Rescorla, E., "HTTP Over TLS", RFC2818, May 2000.
+
+ [HTTP]
+ Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1):
+ Semantics and Content", RFC7231, June 2014.
+
+ [COND]
+ Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1):
+ Conditional Requests", RFC7232, June 2014.
+
+ [RANGE]
+ Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1):
+ Conditional Requests", RFC7233, June 2014.
+
+ [SPDY]
+ Mark Belshe, Roberto Peon, "SPDY Protocol - Draft 3.1", http://
+ www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1,
+ September 2013.
+
+ [JSON-LD]
+ M. Sporny, G. Kellogg, M. Lanthaler, "JSON-LD 1.0", W3C
+ Proposed Recommendation,
+ http://www.w3.org/TR/2014/REC-json-ld-20140116/, January 2014.
+
+ [CORS]
+ van Kesteren, Anne (ed), "Cross-Origin Resource Sharing --
+ W3C Candidate Recommendation 29 January 2013",
+
+
+de Jong [Page 21]
+
+Internet-Draft remoteStorage December 2014
+
+
+ http://www.w3.org/TR/cors/, January 2013.
+
+ [MANIFEST]
+ Mozilla Developer Network (ed), "App manifest -- Revision
+ 330541", https://developer.mozilla.org/en-
+ US/Apps/Build/Manifest$revision/566677, April 2014.
+
+ [DATASTORE]
+ "WebAPI/DataStore", MozillaWiki, retrieved May 2014.
+ https://wiki.mozilla.org/WebAPI/DataStore#Manifest
+
+ [KERBEROS]
+ C. Neuman et al., "The Kerberos Network Authentication Service
+ (V5)", RFC4120, July 2005.
+
+ [BEARER]
+ M. Jones, D. Hardt, "The OAuth 2.0 Authorization Framework:
+ Bearer Token Usage", RFC6750, October 2012.
+
+ [AUTHORING]
+ "Using remoteStorage for web authoring", reSite wiki, retrieved
+ September 2014. https://github.com/michielbdejong/resite/wiki
+ /Using-remoteStorage-for-web-authoring
+
+18. Authors' addresses
+
+ Michiel B. de Jong
+ IndieHosters
+
+ Email: michiel@michielbdejong.com
+
+
+ F. Kooman
+ (independent)
+
+ Email: fkooman@tuxed.net
+
+
+
+
+
+
+
+
+
+de Jong [Page 22]
+
+
+Html markup produced by rfcmarkup 1.111, available from
+https://tools.ietf.org/tools/rfcmarkup/
+
+
diff --git a/src/full_text_parser/readability/tests.rs b/src/full_text_parser/readability/tests.rs
index 9f49b8f..32fe94a 100644
--- a/src/full_text_parser/readability/tests.rs
+++ b/src/full_text_parser/readability/tests.rs
@@ -244,6 +244,11 @@ async fn iab_1() {
run_test("iab-1").await
}
+#[tokio::test]
+async fn ietf_1() {
+ run_test("ietf-1").await
+}
+
#[tokio::test]
async fn webmd_1() {
run_test("webmd-1").await