# 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
      "oc-checksum",           # Owncloud client
      "oc-chunk-offset",       # Owncloud client
      "oc-lazyops",            # 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
}
using Go = import "/go.capnp";
$Go.package("websession");
$Go.import("zenhack.net/go/sandstorm/capnp/websession");
