Permalink
Cannot retrieve contributors at this time
# Sandstorm - Personal Cloud Sandbox | |
# Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors | |
# All rights reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
@0xa8cb0f2f1a756b32; | |
using Cxx = import "/capnp/c++.capnp"; | |
$Cxx.namespace("sandstorm"); | |
using Grain = import "grain.capnp"; | |
using Util = import "util.capnp"; | |
struct HttpStatusDescriptor { | |
id @0 :UInt16; | |
title @1 :Text; | |
} | |
annotation httpStatus @0xaf480a0c6cab8887 (enumerant) :HttpStatusDescriptor; | |
const httpStatusAnnotationId :UInt64 = 0xaf480a0c6cab8887; | |
interface WebSession @0xa50711a14d35a8ce extends(Grain.UiSession) { | |
# A UI session based on the web platform. The user's browser communicates to the server through | |
# HTTP requests. | |
# | |
# Many of the details of HTTP are implemented by the platform and thus not exposed here. For | |
# example, the platform may automatically set last-modified based on the last time the | |
# application's storage was written and may automatically implement etags based on hashing the | |
# content. | |
struct Params { | |
# Startup params for web sessions. See `UiView.newSession()`. | |
basePath @0 :Text; | |
# HTTP URL of the application's root directory as seen by this user, e.g. | |
# "https://ioa5fiu34sm4w.example.com/i7efqesOldepw". Never includes the trailing '/'. Useful | |
# for constructing intra-app link URLs, although in general you should try to use relative URLs | |
# whenever possible. Note that the URL can change from session to session and from user to | |
# user, hence it is only valid for the current session. | |
userAgent @1 :Text; | |
acceptableLanguages @2 :List(Text); | |
# Content of User-Agent and Accept-Language headers. The platform will start a new session if | |
# any of these change. | |
# TODO(soon): Support utility factor (e.g. ";q=0.7"). | |
} | |
get @0 (path :Text, context :Context, ignoreBody :Bool) -> Response; | |
# GET or HEAD request. | |
# | |
# If `ignoreBody` is true, then the caller intends to ignore any content body returned. The | |
# caller may choose to return an empty body. (This is used e.g. for HEAD requests.) | |
post @1 (path :Text, content :PostContent, context :Context) -> Response; | |
put @3 (path :Text, content :PutContent, context :Context) -> Response; | |
delete @4 (path :Text, context :Context) -> Response; | |
patch @17 (path :Text, content :PostContent, context :Context) -> Response; | |
postStreaming @5 (path :Text, mimeType :Text, context :Context, encoding :Text) | |
-> (stream :RequestStream); | |
putStreaming @6 (path :Text, mimeType :Text, context :Context, encoding :Text) | |
-> (stream :RequestStream); | |
# Streaming post/put requests, useful when the input is large. If these throw `unimplemented` | |
# exceptions, the caller should fall back to regular post() / put() on the assumption that the | |
# app doesn't implement streaming. | |
# | |
# The optional `encoding` field represents the Content-Encoding header. | |
openWebSocket @2 (path :Text, context :Context, | |
protocol :List(Text), clientStream :WebSocketStream) | |
-> (protocol :List(Text), serverStream :WebSocketStream); | |
# Open a new WebSocket. `protocol` corresponds to the `Sec-WebSocket-Protocol` header. | |
# `clientStream` is the capability which will receive server -> client messages, while | |
# serverStream represents client -> server. | |
propfind @7 (path :Text, xmlContent :Text, depth :PropfindDepth, context :Context) -> Response; | |
proppatch @8 (path :Text, xmlContent :Text, context :Context) -> Response; | |
mkcol @9 (path :Text, content :PostContent, context :Context) -> Response; | |
copy @10 (path :Text, destination :Text, noOverwrite :Bool, | |
shallow :Bool, context :Context) -> Response; | |
move @11 (path :Text, destination :Text, noOverwrite :Bool, context :Context) -> Response; | |
lock @12 (path :Text, xmlContent :Text, shallow :Bool, context :Context) -> Response; | |
unlock @13 (path :Text, lockToken :Text, context :Context) -> Response; | |
acl @14 (path :Text, xmlContent :Text, context :Context) -> Response; | |
report @15 (path :Text, content :PostContent, context :Context) -> Response; | |
# WebDAV methods | |
# | |
# "destination" is a *path*, but *not* a URI -- the origin is stripped, and there is no leading | |
# '/', just like with the `path` parameter. | |
# "shallow = true" means "Depth: 0" | |
# "noOverwrite = true" means "Overwrite: F"; note that this behaves a precondition -- if the | |
# destination already exists then a preconditionFailed response is returned. | |
# | |
# (These boolean flags were intentionally chosen so that the spec-defined default values are | |
# false.) | |
options @16 (path :Text, context :Context) -> Options; | |
# OPTIONS request. | |
struct Context { | |
# Additional per-request context. | |
cookies @0 :List(Util.KeyValue); | |
responseStream @1 :Util.ByteStream; | |
# Stream to which the app can optionally write the response body. This is only actually | |
# used in the case of a `content` response where the `body` union is set to `stream`. In that | |
# case, after returning from the HTTP method, the app begins writing bytes to `responseStream`. | |
# | |
# Since it's not guaranteed that `responseStream` will be used, and because it would be | |
# confusing to start receiving `write()` calls on it before receiving the HTTP response, | |
# callers should typically initialize this field with a promise. When the response indicates | |
# streaming, the caller can then resolve that promise and start receiving the content. | |
# | |
# Callers are required to provide this capability; apps need not handle it being null. | |
accept @2 :List(AcceptedType); | |
# This corresponds to the Accept header | |
acceptEncoding @9 :List(AcceptedEncoding); | |
# This corresponds to the Accept-Encoding header | |
eTagPrecondition :union { | |
none @4 :Void; # No precondition. | |
exists @5 :Void; # If-Match: * | |
doesntExist @8 :Void; # If-None-Match: * | |
matchesOneOf @6 :List(ETag); # If-Match | |
matchesNoneOf @7 :List(ETag); # If-None-Match | |
} | |
additionalHeaders @3 :List(Header); | |
# Additional headers present in the request. Only whitelisted headers are | |
# permitted. | |
struct Header { | |
name @0 :Text; # lower-cased name | |
value @1 :Text; | |
} | |
const headerWhitelist :List(Text) = [ | |
# Non-standard request headers which are whitelisted for backwards-compatibility | |
# purposes. This whitelist exists to help avoid the need to modify code originally written | |
# without Sandstorm in mind -- especially to avoid modifying client apps. Feel free | |
# to send us pull requests adding additional headers. | |
# Values in this list that end with '*' whitelist a prefix. | |
"x-sandstorm-app-*", # For new headers introduced by Sandstorm apps. | |
"oc-total-length", # Owncloud client | |
"oc-chunk-size", # Owncloud client | |
"x-oc-mtime", # Owncloud client | |
"oc-fileid", # Owncloud client | |
"oc-chunked", # Owncloud client | |
"x-hgarg-*", # Mercurial client | |
"x-phabricator-*", # Phabricator | |
"x-requested-with", # JQuery header used by Rails and other frameworks | |
# Headers used for csrf protection by various frameworks: | |
"x-csrftoken", | |
"x-csrf-token", | |
]; | |
} | |
struct PostContent { | |
# TODO(apibump): Rename this to just `Content` or maybe `RequestContent`. | |
mimeType @0 :Text; | |
content @1 :Data; | |
encoding @2 :Text; # Content-Encoding header (optional). | |
} | |
struct PutContent { | |
# TODO(apibump): Remove this and replace it with `PostContent` (renamed to `Content`). | |
mimeType @0 :Text; | |
content @1 :Data; | |
encoding @2 :Text; # Content-Encoding header (optional). | |
} | |
struct ETag { | |
value @0 :Text; # does not include quotes | |
weak @1 :Bool; | |
# denotes that the resource may not be byte-for-byte identical, but is | |
# semantically equivalent | |
} | |
struct Cookie { | |
# Strings here must not contain ';' nor ','. Also, `name` cannot contain '='. | |
name @0 :Text; | |
value @1 :Text; | |
expires :union { | |
none @2 :Void; | |
absolute @3 :Int64; # Unix timestamp. | |
relative @4 :UInt64; # Seconds relative to time of receipt. | |
} | |
httpOnly @5 :Bool; | |
path @6 :Text; | |
# We don't include "secure" because the platform automatically forces all cookies to be secure. | |
} | |
struct AcceptedType { | |
# In the accept header, there is a list of these elements. | |
# The qValue is optional and defaults to 1. | |
# | |
# For example, the Accept header with value 'text/javascript; q=0.01' would have a mimeType of | |
# "text/javascript" and a qValue of .01. | |
mimeType @0 :Text; | |
qValue @1 :Float32 = 1; | |
} | |
struct AcceptedEncoding { | |
# The Accept-Encoding header contains a list of valid content codings. | |
# Each content coding could be "*", indicating an arbitrary encoding. | |
# Each content coding comes with a qValue, defaulting to 1. | |
# For example, gzip;q=0.5 indicates the "gzip" coding with qValue "0.5" | |
contentCoding @0 :Text; | |
qValue @1 :Float32 = 1; | |
} | |
struct Response { | |
setCookies @0 :List(Cookie); | |
cachePolicy @16 :CachePolicy; | |
enum SuccessCode { | |
# 2xx-level status codes that we allow an app to return. | |
# | |
# We do not permit arbitrary status codes because some have semantic meaning that could | |
# cause browsers to do things we don't expect. An unrecognized status code coming from a | |
# sandboxed HTTP server will translate to 500, except for unrecognized 4xx codes which will | |
# translate to 400. | |
# | |
# It's unclear how useful it is to even allow 201 or 202, but since a browser will certainly | |
# treat them as equivalent to 200, we allow them. | |
ok @0 $httpStatus(id = 200, title = "OK"); | |
created @1 $httpStatus(id = 201, title = "Created"); | |
accepted @2 $httpStatus(id = 202, title = "Accepted"); | |
noContent @3 $httpStatus(id = 204, title = "No Content"); | |
partialContent @4 $httpStatus(id = 206, title = "Partial Content"); | |
multiStatus @5 $httpStatus(id = 207, title = "Multi-Status"); | |
# This seems to fit better here than in the 3xx range | |
notModified @6 $httpStatus(id = 304, title = "Not Modified"); | |
# Not applicable: | |
# 203 Non-Authoritative Information: Only applicable to proxies? | |
# 205 Reset Content: Like 204, but even stranger. | |
# Others: Not standard. | |
} | |
enum ClientErrorCode { | |
# 4xx-level status codes that we allow an app to return. | |
# | |
# It's unclear whether status codes other than 400, 403, and 404 have any real utility; | |
# arguably, all client errors should just use code 400 with an accompanying human-readable | |
# error description. But, since browsers presumably treat them all equivalently to 400, it | |
# seems harmless enough to allow them through. | |
# | |
# An unrecognized 4xx error code coming from a sandboxed HTTP server will translate to 400. | |
badRequest @0 $httpStatus(id = 400, title = "Bad Request"); | |
forbidden @1 $httpStatus(id = 403, title = "Forbidden"); | |
notFound @2 $httpStatus(id = 404, title = "Not Found"); | |
methodNotAllowed @3 $httpStatus(id = 405, title = "Method Not Allowed"); | |
notAcceptable @4 $httpStatus(id = 406, title = "Not Acceptable"); | |
conflict @5 $httpStatus(id = 409, title = "Conflict"); | |
gone @6 $httpStatus(id = 410, title = "Gone"); | |
preconditionFailed @11 $httpStatus(id = 412, title = "Precondition Failed"); | |
requestEntityTooLarge @7 $httpStatus(id = 413, title = "Request Entity Too Large"); | |
requestUriTooLong @8 $httpStatus(id = 414, title = "Request-URI Too Long"); | |
unsupportedMediaType @9 $httpStatus(id = 415, title = "Unsupported Media Type"); | |
imATeapot @10 $httpStatus(id = 418, title = "I'm a teapot"); | |
unprocessableEntity @12 $httpStatus(id = 422, title = "Unprocessable Entity"); | |
# Not applicable: | |
# 401 Unauthorized: We don't do HTTP authentication. | |
# 402 Payment Required: LOL | |
# 407 Proxy Authentication Required: Not a proxy. | |
# 408 Request Timeout: Not possible; the entire request is provided with the call. | |
# 411 Length Required: Request is framed using Cap'n Proto. | |
# 412 Precondition Failed: If we implement preconditions, they should be handled | |
# separately from errors. | |
# 416 Requested Range Not Satisfiable: Ranges not implemented (might be later). | |
# 417 Expectation Failed: Like 412. | |
# Others: Not standard. | |
} | |
union { | |
content :group { | |
# Return content (status code 200, or perhaps 201 or 202). | |
statusCode @10 :SuccessCode; | |
encoding @2 :Text; # Content-Encoding header (optional). | |
language @3 :Text; # Content-Language header (optional). | |
mimeType @4 :Text; # Content-Type header. | |
eTag @17 :ETag; | |
# Optional entity tag for this content. This can be used to express preconditions on future | |
# requests, useful for implementing, for example, cache validation (on GETs) and optimistic | |
# concurrency (on PUTs). See `eTagPrecondition` in `WebSession.Context`. | |
body :union { | |
bytes @5 :Data; | |
stream @6 :Util.Handle; | |
# Indicates that the content will be streamed to the `responseStream` offered in the | |
# call's `Context`. The caller may cancel the stream by dropping the Handle. | |
# | |
# Note that to prevent a grain from being shut down in the middle of a large download, | |
# it is necessary to call ping() on this handle every 60 seconds. | |
} | |
disposition :union { | |
normal @13 :Void; | |
download @14 :Text; # Prompt user to save as given file name. | |
} | |
} | |
noContent :group { | |
# Return successful, but with no content (status codes 204 and 205) | |
shouldResetForm @15 :Bool; | |
# If this is the response to a form submission, should the form be reset to empty? | |
# Distinguishes between HTTP response 204 (False) and 205 (True) | |
eTag @19 :ETag; | |
# Optional entity tag header. Server can send this in a response to a modifying request | |
# to indicate for example the new version of the modified resource. | |
} | |
preconditionFailed :group { | |
# One of the preconditions specified in the request context was not met. | |
# | |
# If the request was a GET or HEAD and the precodition was If-None-Match, then this response | |
# corresponds to HTTP 304 "Not Modified". In all other ctases, this response corresponds to | |
# HTTP 412 "Precondition Failed". (We unify these two HTTP status codes because they really | |
# mean the same thing and should be implemented by the same code.) | |
matchingETag @18 :ETag; | |
# If the precondition failed because the etag matched a tag specified in `matchesNoneOf`, | |
# this is the tag that it matched. For other types of preconditions, this is null. | |
# | |
# (This is in particular used for GET requests where the result is "304 not modified".) | |
} | |
redirect :group { | |
# Redirect to the given URL. | |
# | |
# Note that 3xx-level HTTP responses have specific semantic meanings, therefore we actually | |
# represent that meaning here rather than having a 3xx status code enum. `redirect` | |
# covers only 301, 302 (treated as 303), 303, 307, and 308. Other 3xx status codes | |
# need to be handled in a completely different way, since they are not redirects. | |
isPermanent @1 :Bool; | |
# Is this a permanent (cacheable) redirect? | |
switchToGet @12 :Bool; | |
# Should the user-agent change the method to GET when accessing the new location? | |
# Otherwise, it should repeat the same method as was used for this request. | |
location @11 :Text; | |
# New URL to which to redirect. | |
# | |
# TODO(security): Supervisor should prohibit locations outside the app's host. | |
} | |
clientError :group { | |
# HTTP 4xx-level error. The platform will generate a suitable error page. | |
statusCode @7 :ClientErrorCode; | |
descriptionHtml @8 :Text; | |
# Optional extended description of the error, as an HTML document. | |
# | |
# If the response is not text/html, use nonHtmlContent. | |
# | |
# TODO(apibump): Get rid of this and use only nonHtmlContent. | |
nonHtmlBody @21 :ErrorBody; | |
# Response body, of a type that isn't text/html. If present, descriptionHtml should be | |
# ignored. However, older programs only know about descriptionHtml. | |
} | |
serverError :group { | |
# HTTP 5xx-level error. The platform will generate a suitable error page. | |
# | |
# We don't support status codes here because basically none of them are applicable anyway | |
# except 500. | |
descriptionHtml @9 :Text; | |
# Optional extended description of the error, as an HTML document. | |
# | |
# TODO(apibump): Get rid of this and use only nonHtmlContent. | |
nonHtmlBody @22 :ErrorBody; | |
# Response body, of a type that isn't text/html. If present, descriptionHtml should be | |
# ignored. However, older programs only know about descriptionHtml. | |
} | |
# TODO(someday): Return blob directly from storage, so data doesn't have to stream through | |
# the app? | |
} | |
additionalHeaders @20 :List(Header); | |
# Additional headers present in the reponse. Only whitelisted headers are | |
# permitted. | |
struct Header { | |
name @0 :Text; # lower-cased name | |
value @1 :Text; | |
} | |
struct ErrorBody { | |
data @0 :Data; | |
encoding @1 :Text; # Content-Encoding header (optional). | |
language @2 :Text; # Content-Language header (optional). | |
mimeType @3 :Text; # Content-Type header. | |
} | |
const headerWhitelist :List(Text) = [ | |
# Non-standard response headers which are whitelisted for backwards-compatibility | |
# purposes. This whitelist exists to help avoid the need to modify code originally written | |
# without Sandstorm in mind -- especially to avoid modifying client apps. | |
# Feel free to send us pull requests adding additional headers. | |
# Values in this list that end with '*' whitelist a prefix. | |
"x-sandstorm-app-*", # For new headers introduced by Sandstorm apps. | |
"x-oc-mtime", # Owncloud protocol | |
]; | |
} | |
interface RequestStream extends(Util.ByteStream) { | |
# A streaming request. The request body is streamed in via the methods of ByteStream. | |
getResponse @0 () -> Response; | |
# Get the final HTTP response. The caller should call this immediately, before it has actually | |
# written the request data. | |
# | |
# The method is allowed to return early, e.g. in order to start streaming the response while | |
# the request is still uploading. Thus, full-duplex streaming is supported. This is useful in | |
# some obscure cases. For example, an HTTP server that just encrypts the request could do so | |
# by streaming back the response as the request comes in so that it does not need to buffer the | |
# whole thing. | |
# | |
# If the response is completely transmitted before the request finishes uploading, the caller | |
# may cancel the upload stream by simply dropping the RequestStream object (without calling | |
# done()). Note that in the case of a streaming response, "completely transmitted" means that | |
# the response stream's done() method has been called, or the response stream itself has been | |
# dropped. | |
} | |
interface WebSocketStream { | |
sendBytes @0 (message :Data) -> stream; | |
# Send some bytes. WARNING: At present, we just send the raw bytes of the WebSocket protocol. | |
# In the future, this will be replaced with a `sendMessage()` method that sends one WebSocket | |
# datagram at a time. | |
# | |
# TODO(apibump): Send whole WebSocket messages. | |
} | |
struct CachePolicy { | |
enum Scope { | |
# Defines the scope in which caching is allowed. For security reasons, the resource MUST NOT | |
# be stored in a cache with a broader scope, even if it is never actually served from that | |
# cache. | |
none @0; | |
# This resource must not be stored in any cache. | |
perSession @1; | |
# Caching is allowed on a per-session basis. | |
perUser @2; | |
# Caching is allowed on a per-user basis (across multiple sessions). | |
perAppVersion @3; | |
# Caching is allowed on a per-app-version basis (across all users). This is a | |
# Sandstorm-specific notion. | |
universal @4; | |
# Caching is allowed universally, across all users and versions of the app. | |
} | |
withCheck @0 :Scope; | |
# Within a cache serving this scope or a narrower scope, the resource may be stored in cache, | |
# but if a non-negligible amount of time has gone by since the resource was last validated then | |
# the client must check with the server that the resource hasn't changed (revalidate). | |
# | |
# "A non-negligible amount of time" means something on the order of the network latency between | |
# the client and the server. For example, there is obviously no point in re-validating a cached | |
# resource if it was last validated less than one network round trip ago. For optimization | |
# reasons, we allow this to be expanded a bit -- something like a 15s timeout is OK. Ultimately | |
# it is up to the infrastructure to decide, though; if an app is not OK with this, it should | |
# specify `withCheck` = `none`. | |
permanent @1 :Scope; | |
# Within a cache serving this scope or a narrower scope, the resource may be assumed never to | |
# change, and may be served directly from cache without checking with the server. | |
# | |
# Note that we do not allow specification of a cache duration other than "forever" because in | |
# practice if the resource is mutable at all, you almost certainly don't know when it will next | |
# change, and so setting a non-zero cache duration will lead to stale data bugs. | |
variesOnCookie @2 :Bool; | |
variesOnAccept @3 :Bool; | |
# Indicates what inputs in `Context` would have caused a different response to be served. | |
# If these are false and caching is enabled, it is assumed the resource is identical regardless | |
# of these inputs. | |
} | |
struct Options { | |
davClass1 @0 :Bool = false; | |
davClass2 @1 :Bool = false; | |
davClass3 @2 :Bool = false; | |
davExtensions @3 :List(Text); | |
} | |
enum PropfindDepth { | |
infinity @0 $Cxx.name("infinity_"); # INFINITY is a macro in C | |
zero @1; | |
one @2; | |
} | |
# Request headers that we will probably add later: | |
# * Caching: | |
# * Cache-Control | |
# * If-* | |
# * Range requests: | |
# * Range | |
# | |
# Request headers that could be added later, but don't seem terribly important: | |
# * Accept | |
# * Accept-Charset | |
# * Accept-Encoding | |
# * Content-MD5 (MD5 is dead; perhaps we could introduce a modern alternative) | |
# * Date | |
# * From | |
# * Max-Forwards | |
# * Warning | |
# * Pragma | |
# | |
# Request headers which will NOT be added ever: | |
# * Sandstorm handles authorization: | |
# * Authorization | |
# * Sandstorm defines cross-origin request permissions: | |
# * Access-Control-Request-Headers | |
# * Access-Control-Request-Method | |
# * Origin | |
# * Redundant or irrelevant to Cap'n Proto RPC: | |
# * Connection | |
# * Content-Length | |
# * Expect | |
# * Host | |
# * Keep-Alive | |
# * TE | |
# * Trailer | |
# * Transfer-Encoding | |
# * Upgrade | |
# * Apps should not have this information: | |
# * Referer | |
# * Via | |
# * Proxy-* | |
# * Sec-* | |
# * Sandstorm already prevents illicit tracking technically; no need for policy: | |
# * DNT | |
# Response headers that we will probably add later: | |
# * Caching: | |
# * Age | |
# * Cange-Control | |
# * ETag | |
# * Expires | |
# * Last-Modified | |
# * Vary (but Sandstorm will always add "Authorization") | |
# * Range requests: | |
# * Accept-Ranges | |
# * Content-Range | |
# | |
# Response headers that could be added later, but don't seem terribly important: | |
# * Allow | |
# * Content-Location | |
# * Content-MD5 - MD5 is dead; perhaps we could introduce a modern alternative. | |
# * Content-Disposition (filename part only) | |
# * Link | |
# * Pragma | |
# * Refresh | |
# * Retry-After | |
# * Server | |
# * Via | |
# * Warning | |
# | |
# Response headers which will NEVER be implemented: | |
# * Sandstorm defines cross-origin request permissions: | |
# * Access-Control-* | |
# * Sandstorm uses these for sandboxing: | |
# * Content-Security-Policy | |
# * X-Frame-Options | |
# * Redundant or irrelevant to Cap'n Proto RPC: | |
# * Connection | |
# * Content-Length - Redundant. | |
# * Trailer | |
# * Transfer-Encoding | |
# * Upgrade | |
# * These belong to the domain owner, not the app: | |
# * Public-Key-Pins | |
# * Strict-Transport-Security | |
# * Sandstorm controls authentication: | |
# * WWW-Authenticate | |
# * Irrelevant to servers: | |
# * Proxy-Authenticate | |
} |