changeset 1465:2f5fd248588d

getHeader and setHeader
author Adam Chlipala <adam@chlipala.net>
date Sun, 29 May 2011 14:29:26 -0400
parents 969b90b1f2f9
children e2d7bd41f527
files doc/manual.tex include/types.h include/urweb.h lib/ur/basis.urs src/c/urweb.c src/cjr_print.sml src/compiler.sig src/compiler.sml src/demo.sml src/mono_opt.sml src/settings.sig src/settings.sml tests/headers.ur tests/headers.urp tests/headers.urs
diffstat 15 files changed, 191 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/doc/manual.tex	Sun May 29 13:31:53 2011 -0400
+++ b/doc/manual.tex	Sun May 29 14:29:26 2011 -0400
@@ -136,7 +136,7 @@
 
 Here is the complete list of directive forms.  ``FFI'' stands for ``foreign function interface,'' Ur's facility for interaction between Ur programs and C and JavaScript libraries.
 \begin{itemize}
-\item \texttt{[allow|deny] [url|mime] PATTERN} registers a rule governing which URLs or MIME types are allowed in this application.  The first such rule to match a URL or MIME type determines the verdict.  If \texttt{PATTERN} ends in \texttt{*}, it is interpreted as a prefix rule.  Otherwise, a string must match it exactly.
+\item \texttt{[allow|deny] [url|mime|requestHeader|responseHeader] PATTERN} registers a rule governing which URLs, MIME types, HTTP request headers, or HTTP response headers are allowed to appear explicitly in this application.  The first such rule to match a name determines the verdict.  If \texttt{PATTERN} ends in \texttt{*}, it is interpreted as a prefix rule.  Otherwise, a string must match it exactly.
 \item \texttt{alwaysInline PATH} requests that every call to the referenced function be inlined.  Section \ref{structure} explains how functions are assigned path strings.
 \item \texttt{benignEffectful Module.ident} registers an FFI function or transaction as having side effects.  The optimizer avoids removing, moving, or duplicating calls to such functions.  Every effectful FFI function must be registered, or the optimizer may make invalid transformations.  This version of the \texttt{effectful} directive registers that this function only has side effects that remain local to a single page generation.
 \item \texttt{clientOnly Module.ident} registers an FFI function or transaction that may only be run in client browsers.
@@ -1406,6 +1406,20 @@
   \mt{val} \; \mt{fileData} : \mt{file} \to \mt{blob}
 \end{array}$$
 
+It is also possible to get HTTP request headers and set HTTP response headers, using abstract types similar to the one for URLs.
+
+$$\begin{array}{l}
+  \mt{type} \; \mt{requestHeader} \\
+  \mt{val} \; \mt{blessRequestHeader} : \mt{string} \to \mt{requestHeader} \\
+  \mt{val} \; \mt{checkRequestHeader} : \mt{string} \to \mt{option} \; \mt{requestHeader} \\
+  \mt{val} \; \mt{getHeader} : \mt{requestHeader} \to \mt{transaction} \; (\mt{option} \; \mt{string}) \\
+  \\
+  \mt{type} \; \mt{responseHeader} \\
+  \mt{val} \; \mt{blessResponseHeader} : \mt{string} \to \mt{responseHeader} \\
+  \mt{val} \; \mt{checkResponseHeader} : \mt{string} \to \mt{option} \; \mt{responseHeader} \\
+  \mt{val} \; \mt{setHeader} : \mt{responseHeader} \to \mt{string} \to \mt{transaction} \; \mt{unit}
+\end{array}$$
+
 A blob can be extracted from a file and returned as the page result.  There are bless and check functions for MIME types analogous to those for URLs.
 $$\begin{array}{l}
   \mt{type} \; \mt{mimeType} \\
--- a/include/types.h	Sun May 29 13:31:53 2011 -0400
+++ b/include/types.h	Sun May 29 14:29:26 2011 -0400
@@ -90,6 +90,8 @@
   uw_Basis_string (*cookie_sig)(uw_context);
   int (*check_url)(const char *);
   int (*check_mime)(const char *);
+  int (*check_requestHeader)(const char *);
+  int (*check_responseHeader)(const char *);
 
   void (*on_error)(uw_context, char *);
 
--- a/include/urweb.h	Sun May 29 13:31:53 2011 -0400
+++ b/include/urweb.h	Sun May 29 14:29:26 2011 -0400
@@ -217,9 +217,16 @@
 
 uw_Basis_string uw_Basis_bless(uw_context, uw_Basis_string);
 uw_Basis_string uw_Basis_blessMime(uw_context, uw_Basis_string);
+uw_Basis_string uw_Basis_blessRequestHeader(uw_context, uw_Basis_string);
+uw_Basis_string uw_Basis_blessResponseHeader(uw_context, uw_Basis_string);
 
 uw_Basis_string uw_Basis_checkUrl(uw_context, uw_Basis_string);
 uw_Basis_string uw_Basis_checkMime(uw_context, uw_Basis_string);
+uw_Basis_string uw_Basis_checkRequestHeader(uw_context, uw_Basis_string);
+uw_Basis_string uw_Basis_checkResponseHeader(uw_context, uw_Basis_string);
+
+uw_Basis_string uw_Basis_getHeader(uw_context, uw_Basis_string name);
+uw_unit uw_Basis_setHeader(uw_context, uw_Basis_string name, uw_Basis_string value);
 
 uw_Basis_string uw_unnull(uw_Basis_string);
 uw_Basis_string uw_Basis_makeSigString(uw_context, uw_Basis_string);
--- a/lib/ur/basis.urs	Sun May 29 13:31:53 2011 -0400
+++ b/lib/ur/basis.urs	Sun May 29 14:29:26 2011 -0400
@@ -163,6 +163,16 @@
                                                 Secure : bool} -> transaction unit
 val clearCookie : t ::: Type -> http_cookie t -> transaction unit
 
+type requestHeader
+val blessRequestHeader : string -> requestHeader
+val checkRequestHeader : string -> option requestHeader
+val getHeader : requestHeader -> transaction (option string)
+
+type responseHeader
+val blessResponseHeader : string -> responseHeader
+val checkResponseHeader : string -> option responseHeader
+val setHeader : responseHeader -> string -> transaction unit
+
 
 (** JavaScript-y gadgets *)
 
--- a/src/c/urweb.c	Sun May 29 13:31:53 2011 -0400
+++ b/src/c/urweb.c	Sun May 29 14:29:26 2011 -0400
@@ -3346,7 +3346,7 @@
     return NULL;
 }
 
-int mime_format(const char *s) {
+static int mime_format(const char *s) {
   for (; *s; ++s)
     if (!isalnum((int)*s) && *s != '/' && *s != '-' && *s != '.')
       return 0;
@@ -3374,6 +3374,70 @@
     return NULL;
 }
 
+uw_Basis_string uw_Basis_blessRequestHeader(uw_context ctx, uw_Basis_string s) {
+  if (!mime_format(s))
+    uw_error(ctx, FATAL, "Request header \"%s\" contains invalid character", uw_Basis_htmlifyString(ctx, s));
+
+  if (ctx->app->check_requestHeader(s))
+    return s;
+  else
+    uw_error(ctx, FATAL, "Disallowed request header %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_string uw_Basis_checkRequestHeader(uw_context ctx, uw_Basis_string s) {
+  if (!mime_format(s))
+    return NULL;
+
+  if (ctx->app->check_requestHeader(s))
+    return s;
+  else
+    return NULL;
+}
+
+uw_Basis_string uw_Basis_blessResponseHeader(uw_context ctx, uw_Basis_string s) {
+  if (!mime_format(s))
+    uw_error(ctx, FATAL, "Response header \"%s\" contains invalid character", uw_Basis_htmlifyString(ctx, s));
+
+  if (ctx->app->check_responseHeader(s))
+    return s;
+  else
+    uw_error(ctx, FATAL, "Disallowed response header %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_string uw_Basis_checkResponseHeader(uw_context ctx, uw_Basis_string s) {
+  if (!mime_format(s))
+    return NULL;
+
+  if (ctx->app->check_responseHeader(s))
+    return s;
+  else
+    return NULL;
+}
+
+uw_Basis_string uw_Basis_getHeader(uw_context ctx, uw_Basis_string name) {
+  return uw_Basis_requestHeader(ctx, name);
+}
+
+static int mime_value_format(const char *s) {
+  for (; *s; ++s)
+    if (*s == '\r' || *s == '\n')
+      return 0;
+
+  return 1;
+}
+
+uw_unit uw_Basis_setHeader(uw_context ctx, uw_Basis_string name, uw_Basis_string value) {
+  if (!mime_value_format(value))
+    uw_error(ctx, FATAL, "Invalid value for HTTP response header");
+
+  uw_write_header(ctx, name);
+  uw_write_header(ctx, ": ");
+  uw_write_header(ctx, value);
+  uw_write_header(ctx, "\r\n");
+
+  return uw_unit_v;
+}
+
 uw_Basis_string uw_unnull(uw_Basis_string s) {
   return s ? s : "";
 }
--- a/src/cjr_print.sml	Sun May 29 13:31:53 2011 -0400
+++ b/src/cjr_print.sml	Sun May 29 14:29:26 2011 -0400
@@ -2986,6 +2986,12 @@
 
              makeChecker ("uw_check_mime", Settings.getMimeRules ()),
              newline,
+
+             makeChecker ("uw_check_requestHeader", Settings.getRequestHeaderRules ()),
+             newline,
+
+             makeChecker ("uw_check_responseHeader", Settings.getResponseHeaderRules ()),
+             newline,
              
              string "extern void uw_sign(const char *in, char *out);",
              newline,
@@ -3122,7 +3128,7 @@
                          "uw_client_init", "uw_initializer", "uw_expunger",
                          "uw_db_init", "uw_db_begin", "uw_db_commit", "uw_db_rollback", "uw_db_close",
                          "uw_handle",
-                         "uw_input_num", "uw_cookie_sig", "uw_check_url", "uw_check_mime",
+                         "uw_input_num", "uw_cookie_sig", "uw_check_url", "uw_check_mime", "uw_check_requestHeader", "uw_check_responseHeader",
                          case onError of NONE => "NULL" | SOME _ => "uw_onError", "my_periodics"],
              string "};",
              newline]
--- a/src/compiler.sig	Sun May 29 13:31:53 2011 -0400
+++ b/src/compiler.sig	Sun May 29 14:29:26 2011 -0400
@@ -51,6 +51,8 @@
          rewrites : Settings.rewrite list,
          filterUrl : Settings.rule list,
          filterMime : Settings.rule list,
+         filterRequest : Settings.rule list,
+         filterResponse : Settings.rule list,
          protocol : string option,
          dbms : string option,
          sigFile : string option,
--- a/src/compiler.sml	Sun May 29 13:31:53 2011 -0400
+++ b/src/compiler.sml	Sun May 29 14:29:26 2011 -0400
@@ -55,6 +55,8 @@
      rewrites : Settings.rewrite list,
      filterUrl : Settings.rule list,
      filterMime : Settings.rule list,
+     filterRequest : Settings.rule list,
+     filterResponse : Settings.rule list,
      protocol : string option,
      dbms : string option,
      sigFile : string option,
@@ -335,6 +337,8 @@
      Settings.setRewriteRules (#rewrites job);
      Settings.setUrlRules (#filterUrl job);
      Settings.setMimeRules (#filterMime job);
+     Settings.setRequestHeaderRules (#filterRequest job);
+     Settings.setResponseHeaderRules (#filterResponse job);
      Option.app Settings.setProtocol (#protocol job);
      Option.app Settings.setDbms (#dbms job);
      Settings.setSafeGets (#safeGets job);
@@ -384,6 +388,8 @@
                                     from = capitalize (OS.Path.file fname) ^ "/", to = ""}],
                        filterUrl = [],
                        filterMime = [],
+                       filterRequest = [],
+                       filterResponse = [],
                        protocol = NONE,
                        dbms = NONE,
                        sigFile = NONE,
@@ -497,6 +503,8 @@
                     val rewrites = ref []
                     val url = ref []
                     val mime = ref []
+                    val request = ref []
+                    val response = ref []
                     val libs = ref []
                     val protocol = ref NONE
                     val dbms = ref NONE
@@ -529,6 +537,8 @@
                                 rewrites = rev (!rewrites),
                                 filterUrl = rev (!url),
                                 filterMime = rev (!mime),
+                                filterRequest = rev (!request),
+                                filterResponse = rev (!response),
                                 sources = sources,
                                 protocol = !protocol,
                                 dbms = !dbms,
@@ -573,6 +583,8 @@
                                 rewrites = #rewrites old @ #rewrites new,
                                 filterUrl = #filterUrl old @ #filterUrl new,
                                 filterMime = #filterMime old @ #filterMime new,
+                                filterRequest = #filterRequest old @ #filterRequest new,
+                                filterResponse = #filterResponse old @ #filterResponse new,
                                 sources = #sources new
                                           @ List.filter (fn s => List.all (fn s' => s' <> s) (#sources new))
                                                         (#sources old),
@@ -613,6 +625,8 @@
                         case s of
                             "url" => url
                           | "mime" => mime
+                          | "requestHeader" => request
+                          | "responseHeader" => response
                           | _ => (ErrorMsg.error "Bad filter kind";
                                   url)
 
--- a/src/demo.sml	Sun May 29 13:31:53 2011 -0400
+++ b/src/demo.sml	Sun May 29 14:29:26 2011 -0400
@@ -114,6 +114,8 @@
             rewrites = #rewrites combined @ #rewrites urp,
             filterUrl = #filterUrl combined @ #filterUrl urp,
             filterMime = #filterMime combined @ #filterMime urp,
+            filterRequest = #filterRequest combined @ #filterRequest urp,
+            filterResponse = #filterResponse combined @ #filterResponse urp,
             protocol = mergeWith #2 (#protocol combined, #protocol urp),
             dbms = mergeWith #2 (#dbms combined, #dbms urp),
             sigFile = mergeWith #2 (#sigFile combined, #sigFile urp),
--- a/src/mono_opt.sml	Sun May 29 13:31:53 2011 -0400
+++ b/src/mono_opt.sml	Sun May 29 14:29:26 2011 -0400
@@ -435,6 +435,33 @@
          else
              ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessMime'");
          se)
+      | EFfiApp ("Basis", "checkMime", [(se as EPrim (Prim.String s), loc)]) =>
+        (if Settings.checkMime s then
+             ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+         else
+             ENone (TFfi ("Basis", "string"), loc))
+      | EFfiApp ("Basis", "blessRequestHeader", [(se as EPrim (Prim.String s), loc)]) =>
+        (if Settings.checkRequestHeader s then
+             ()
+         else
+             ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessRequestHeader'");
+         se)
+      | EFfiApp ("Basis", "checkRequestHeader", [(se as EPrim (Prim.String s), loc)]) =>
+        (if Settings.checkRequestHeader s then
+             ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+         else
+             ENone (TFfi ("Basis", "string"), loc))
+      | EFfiApp ("Basis", "blessResponseHeader", [(se as EPrim (Prim.String s), loc)]) =>
+        (if Settings.checkResponseHeader s then
+             ()
+         else
+             ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessResponseHeader'");
+         se)
+      | EFfiApp ("Basis", "checkResponseHeader", [(se as EPrim (Prim.String s), loc)]) =>
+        (if Settings.checkResponseHeader s then
+             ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+         else
+             ENone (TFfi ("Basis", "string"), loc))
 
       | EFfiApp ("Basis", "checkString", [(EPrim (Prim.String s), loc)]) => 
         let
--- a/src/settings.sig	Sun May 29 13:31:53 2011 -0400
+++ b/src/settings.sig	Sun May 29 14:29:26 2011 -0400
@@ -96,6 +96,14 @@
     val getMimeRules : unit -> rule list
     val checkMime : string -> bool
 
+    val setRequestHeaderRules : rule list -> unit
+    val getRequestHeaderRules : unit -> rule list
+    val checkRequestHeader : string -> bool
+
+    val setResponseHeaderRules : rule list -> unit
+    val getResponseHeaderRules : unit -> rule list
+    val checkResponseHeader : string -> bool
+
     (* Web protocols that generated programs may speak *)
     type protocol = {
         name : string,       (* Call it this on the command line *)
--- a/src/settings.sml	Sun May 29 13:31:53 2011 -0400
+++ b/src/settings.sml	Sun May 29 14:29:26 2011 -0400
@@ -140,7 +140,9 @@
                         "debug",
                         "naughtyDebug",
                         "rand",
-                        "now"]
+                        "now",
+                        "getHeader",
+                        "setHeader"]
 
 val benign = ref benignBase
 fun setBenignEffectful ls = benign := S.addList (benignBase, ls)
@@ -293,12 +295,18 @@
 
 val url = ref ([] : rule list)
 val mime = ref ([] : rule list)
+val request = ref ([] : rule list)
+val response = ref ([] : rule list)
 
 fun setUrlRules ls = url := ls
 fun setMimeRules ls = mime := ls
+fun setRequestHeaderRules ls = request := ls
+fun setResponseHeaderRules ls = response := ls
 
 fun getUrlRules () = !url
 fun getMimeRules () = !mime
+fun getRequestHeaderRules () = !request
+fun getResponseHeaderRules () = !response
 
 fun check f rules s =
     let
@@ -324,9 +332,12 @@
     end
 
 val checkUrl = check (fn _ => true) url
-val checkMime = check
-                    (CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"/" orelse ch = #"-" orelse ch = #"."))
-                    mime
+
+val validMime = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"/" orelse ch = #"-" orelse ch = #".")
+
+val checkMime = check validMime mime
+val checkRequestHeader = check validMime request
+val checkResponseHeader = check validMime response
 
 
 type protocol = {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/headers.ur	Sun May 29 14:29:26 2011 -0400
@@ -0,0 +1,11 @@
+fun action () =
+  setHeader (blessResponseHeader "Location") "http://www.google.com/";
+  return <xml/>
+
+fun main () =
+  ag <- getHeader (blessRequestHeader "User-Agent");
+  return <xml><body>
+    User agent: {[ag]}
+
+    <form> <submit action={action}/> </form>
+  </body></xml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/headers.urp	Sun May 29 14:29:26 2011 -0400
@@ -0,0 +1,5 @@
+rewrite url Headers/*
+allow requestHeader User-Agent
+allow responseHeader Location
+
+headers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/headers.urs	Sun May 29 14:29:26 2011 -0400
@@ -0,0 +1,1 @@
+val main : unit -> transaction page