Mercurial > urweb
view src/settings.sml @ 2127:8beeb4f761b5
Fix interpretation of 'file' argument paths
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Sun, 08 Mar 2015 10:55:05 -0400 |
parents | 1f1575eff4b9 |
children | e8863d9f8c59 e10881cd92da |
line wrap: on
line source
(* Copyright (c) 2008-2011, Adam Chlipala * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - The names of contributors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. *) structure Settings :> SETTINGS = struct val configBin = ref Config.bin val configLib = ref Config.lib val configSrcLib = ref Config.srclib val configInclude = ref Config.includ val configSitelisp = ref Config.sitelisp val configCCompiler = ref Config.ccompiler fun getCCompiler () = !configCCompiler fun setCCompiler cc = configCCompiler := cc fun libUr () = OS.Path.joinDirFile {dir = !configSrcLib, file = "ur"} fun libC () = OS.Path.joinDirFile {dir = !configSrcLib, file = "c"} fun libJs () = OS.Path.joinDirFile {dir = !configSrcLib, file = "js"} fun libFile s = OS.Path.joinDirFile {dir = libUr (), file = s} val urlPrefixFull = ref "/" val urlPrefix = ref "/" val urlPrePrefix = ref "" val timeout = ref 0 val headers = ref ([] : string list) val scripts = ref ([] : string list) fun getUrlPrefixFull () = !urlPrefixFull fun getUrlPrefix () = !urlPrefix fun getUrlPrePrefix () = !urlPrePrefix fun setUrlPrefix p = let val prefix = if p = "" then "/" else if String.sub (p, size p - 1) <> #"/" then p ^ "/" else p fun findPrefix n = let val (befor, after) = Substring.splitl (fn ch => ch <> #"/") (Substring.extract (prefix, n, NONE)) in if Substring.isEmpty after then ("", prefix) else (String.substring (prefix, 0, n) ^ Substring.string befor, Substring.string after) end val (prepre, prefix) = if String.isPrefix "http://" prefix then findPrefix 7 else if String.isPrefix "https://" prefix then findPrefix 8 else ("", prefix) in urlPrefixFull := p; urlPrePrefix := prepre; urlPrefix := prefix end fun getTimeout () = !timeout fun setTimeout n = timeout := n fun getHeaders () = !headers fun setHeaders ls = headers := ls fun getScripts () = !scripts fun setScripts ls = scripts := ls type ffi = string * string structure K = struct type ord_key = ffi fun compare ((m1, x1), (m2, x2)) = Order.join (String.compare (m1, m2), fn () => String.compare (x1, x2)) end structure S = BinarySetFn(K) structure M = BinaryMapFn(K) fun basis x = S.addList (S.empty, map (fn x : string => ("Basis", x)) x) val clientToServerBase = basis ["int", "float", "string", "time", "file", "unit", "option", "list", "bool", "variant"] val clientToServer = ref clientToServerBase fun setClientToServer ls = clientToServer := S.addList (clientToServerBase, ls) fun mayClientToServer x = S.member (!clientToServer, x) val effectfulBase = basis ["dml", "nextval", "setval", "set_cookie", "clear_cookie", "new_channel", "send", "htmlifyInt_w", "htmlifyFloat_w", "htmlifyString_w", "htmlifyBool_w", "htmlifyTime_w", "attrifyInt_w", "attrifyFloat_w", "attrifyString_w", "attrifyChar_w", "urlifyInt_w", "urlifyFloat_w", "urlifyString_w", "urlifyBool_w", "urlifyChannel_w"] val effectful = ref effectfulBase fun setEffectful ls = effectful := S.addList (effectfulBase, ls) fun isEffectful x = S.member (!effectful, x) fun addEffectful x = effectful := S.add (!effectful, x) val benignBase = basis ["get_cookie", "new_client_source", "get_client_source", "set_client_source", "current", "alert", "confirm", "onError", "onFail", "onConnectFail", "onDisconnect", "onServerError", "mouseEvent", "keyEvent", "debug", "rand", "now", "getHeader", "setHeader", "spawn", "onClick", "onDblclick", "onKeydown", "onKeypress", "onKeyup", "onMousedown", "onMousemove", "onMouseout", "onMouseover", "onMouseup", "preventDefault", "stopPropagation", "fresh", "giveFocus", "currentUrlHasPost", "currentUrlHasQueryString", "currentUrl"] val benign = ref benignBase fun setBenignEffectful ls = benign := S.addList (benignBase, ls) fun addBenignEffectful x = benign := S.add (!benign, x) fun isBenignEffectful x = S.member (!benign, x) val clientBase = basis ["get_client_source", "current", "alert", "confirm", "recv", "sleep", "spawn", "onError", "onFail", "onConnectFail", "onDisconnect", "onServerError", "mouseEvent", "keyEvent", "onClick", "onDblclick", "onKeydown", "onKeypress", "onKeyup", "onMousedown", "onMousemove", "onMouseout", "onMouseover", "onMouseup", "preventDefault", "stopPropagation", "giveFocus"] val client = ref clientBase fun setClientOnly ls = client := S.addList (clientBase, ls) fun addClientOnly x = client := S.add (!client, x) fun isClientOnly x = S.member (!client, x) val serverBase = basis ["requestHeader", "query", "dml", "nextval", "setval", "channel", "send", "fieldName", "fieldValue", "remainingFields", "firstFormField"] val server = ref serverBase fun setServerOnly ls = server := S.addList (serverBase, ls) fun addServerOnly x = server := S.add (!server, x) fun isServerOnly x = S.member (!server, x) val basisM = foldl (fn ((k, v : string), m) => M.insert (m, ("Basis", k), v)) M.empty val jsFuncsBase = basisM [("alert", "alert"), ("stringToTime", "stringToTime"), ("stringToTime_error", "stringToTime_error"), ("timef", "strftime"), ("confirm", "confrm"), ("get_client_source", "sg"), ("current", "scur"), ("htmlifyBool", "bs"), ("htmlifyFloat", "ts"), ("htmlifyInt", "ts"), ("htmlifyString", "eh"), ("new_client_source", "sc"), ("set_client_source", "sv"), ("stringToFloat", "pflo"), ("stringToInt", "pio"), ("stringToFloat_error", "pfl"), ("stringToInt_error", "pi"), ("urlifyInt", "ts"), ("urlifyFloat", "ts"), ("urlifyTime", "ts"), ("urlifyString", "uf"), ("urlifyBool", "ub"), ("recv", "rv"), ("strcat", "cat"), ("intToString", "ts"), ("floatToString", "ts"), ("charToString", "ts"), ("onError", "onError"), ("onFail", "onFail"), ("onConnectFail", "onConnectFail"), ("onDisconnect", "onDisconnect"), ("onServerError", "onServerError"), ("attrifyString", "atr"), ("attrifyInt", "ts"), ("attrifyFloat", "ts"), ("attrifyBool", "bs"), ("boolToString", "ts"), ("str1", "id"), ("strsub", "sub"), ("strsuffix", "suf"), ("strlen", "slen"), ("strindex", "sidx"), ("strsindex", "ssidx"), ("strchr", "schr"), ("substring", "ssub"), ("strcspn", "sspn"), ("strlenGe", "strlenGe"), ("mouseEvent", "uw_mouseEvent"), ("keyEvent", "uw_keyEvent"), ("minTime", "0"), ("stringToBool_error", "s2be"), ("stringToBool", "s2b"), ("islower", "isLower"), ("isupper", "isUpper"), ("isalpha", "isAlpha"), ("isdigit", "isDigit"), ("isalnum", "isAlnum"), ("isblank", "isBlank"), ("isspace", "isSpace"), ("isxdigit", "isXdigit"), ("isprint", "isPrint"), ("tolower", "toLower"), ("toupper", "toUpper"), ("ord", "ord"), ("checkUrl", "checkUrl"), ("bless", "bless"), ("blessData", "blessData"), ("eq_time", "eq"), ("lt_time", "lt"), ("le_time", "le"), ("debug", "uw_debug"), ("naughtyDebug", "uw_debug"), ("floatFromInt", "float"), ("ceil", "ceil"), ("trunc", "trunc"), ("round", "round"), ("now", "now"), ("timeToString", "showTime"), ("htmlifyTime", "showTimeHtml"), ("toSeconds", "toSeconds"), ("addSeconds", "addSeconds"), ("diffInSeconds", "diffInSeconds"), ("toMilliseconds", "toMilliseconds"), ("diffInMilliseconds", "diffInMilliseconds"), ("fromDatetime", "fromDatetime"), ("datetimeYear", "datetimeYear"), ("datetimeMonth", "datetimeMonth"), ("datetimeDay", "datetimeDay"), ("datetimeHour", "datetimeHour"), ("datetimeMinute", "datetimeMinute"), ("datetimeSecond", "datetimeSecond"), ("datetimeDayOfWeek", "datetimeDayOfWeek"), ("onClick", "uw_onClick"), ("onDblclick", "uw_onDblclick"), ("onKeydown", "uw_onKeydown"), ("onKeypress", "uw_onKeypress"), ("onKeyup", "uw_onKeyup"), ("onMousedown", "uw_onMousedown"), ("onMousemove", "uw_onMousemove"), ("onMouseout", "uw_onMouseout"), ("onMouseover", "uw_onMouseover"), ("onMouseup", "uw_onMouseup"), ("preventDefault", "uw_preventDefault"), ("stopPropagation", "uw_stopPropagation"), ("fresh", "fresh"), ("atom", "atom"), ("css_url", "css_url"), ("property", "property"), ("giveFocus", "giveFocus")] val jsFuncs = ref jsFuncsBase fun setJsFuncs ls = jsFuncs := foldl (fn ((k, v), m) => M.insert (m, k, v)) jsFuncsBase ls fun jsFunc x = M.find (!jsFuncs, x) fun addJsFunc (k, v) = jsFuncs := M.insert (!jsFuncs, k, v) fun allJsFuncs () = M.listItemsi (!jsFuncs) datatype pattern_kind = Exact | Prefix datatype action = Allow | Deny type rule = { action : action, kind : pattern_kind, pattern : string } datatype path_kind = Any | Url | Table | Sequence | View | Relation | Cookie | Style type rewrite = { pkind : path_kind, kind : pattern_kind, from : string, to : string, hyphenate : bool } fun pak2s pak = case pak of Exact => "Exact" | Prefix => "Prefix" fun pk2s pk = case pk of Any => "Any" | Url => "Url" | Table => "Table" | Sequence => "Sequence" | View => "View" | Relation => "Relation" | Cookie => "Cookie" | Style => "Style" fun r2s (r : rewrite) = pak2s (#kind r) ^ " " ^ pk2s (#pkind r) ^ ", from<" ^ #from r ^ ">, to<" ^ #to r ^ ">" val rewrites = ref ([] : rewrite list) fun subsume (pk1, pk2) = pk1 = pk2 orelse pk2 = Any orelse pk2 = Relation andalso (pk1 = Table orelse pk1 = Sequence orelse pk1 = View) fun setRewriteRules ls = rewrites := ls fun rewrite pk s = let fun rew (ls : rewrite list) = case ls of [] => s | rewr :: ls => let fun match () = case #kind rewr of Exact => if #from rewr = s then SOME (size s) else NONE | Prefix => if String.isPrefix (#from rewr) s then SOME (size (#from rewr)) else NONE in if subsume (pk, #pkind rewr) then case match () of NONE => rew ls | SOME suffixStart => let val s = #to rewr ^ String.extract (s, suffixStart, NONE) in if #hyphenate rewr then String.translate (fn #"_" => "-" | ch => str ch) s else s end else rew ls end in rew (!rewrites) end val url = ref ([] : rule list) val mime = ref ([] : rule list) val request = ref ([] : rule list) val response = ref ([] : rule list) val env = ref ([] : rule list) fun setUrlRules ls = url := ls fun setMimeRules ls = mime := ls fun setRequestHeaderRules ls = request := ls fun setResponseHeaderRules ls = response := ls fun setEnvVarRules ls = env := ls fun getUrlRules () = !url fun getMimeRules () = !mime fun getRequestHeaderRules () = !request fun getResponseHeaderRules () = !response fun getEnvVarRules () = !env fun check f rules s = let fun chk (ls : rule list) = case ls of [] => false | rule :: ls => let val matches = case #kind rule of Exact => #pattern rule = s | Prefix => String.isPrefix (#pattern rule) s in if matches then case #action rule of Allow => true | Deny => false else chk ls end in f s andalso chk (!rules) end val checkUrl = check (fn _ => true) url val validMime = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"/" orelse ch = #"-" orelse ch = #"." orelse ch = #"+") val validEnv = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"_" orelse ch = #".") val checkMime = check validMime mime val checkRequestHeader = check validMime request val checkResponseHeader = check validMime response val checkEnvVar = check validEnv env type protocol = { name : string, compile : string, linkStatic : string, linkDynamic : string, persistent : bool, code : unit -> Print.PD.pp_desc } val protocols = ref ([] : protocol list) fun addProtocol p = protocols := p :: !protocols fun getProtocol s = List.find (fn p => #name p = s) (!protocols) fun clibFile s = OS.Path.joinDirFile {dir = libC (), file = s} val curProto = ref {name = "", compile = "", linkStatic = "", linkDynamic = "", persistent = false, code = fn () => Print.box []} fun setProtocol name = case getProtocol name of NONE => raise Fail ("Unknown protocol " ^ name) | SOME p => curProto := p fun currentProtocol () = !curProto val debug = ref false fun setDebug b = debug := b fun getDebug () = !debug datatype sql_type = Int | Float | String | Char | Bool | Time | Blob | Channel | Client | Nullable of sql_type fun p_sql_ctype t = let open Print.PD open Print in case t of Int => "uw_Basis_int" | Float => "uw_Basis_float" | String => "uw_Basis_string" | Char => "uw_Basis_char" | Bool => "uw_Basis_bool" | Time => "uw_Basis_time" | Blob => "uw_Basis_blob" | Channel => "uw_Basis_channel" | Client => "uw_Basis_client" | Nullable String => "uw_Basis_string" | Nullable t => p_sql_ctype t ^ "*" end fun isBlob Blob = true | isBlob (Nullable t) = isBlob t | isBlob _ = false fun isNotNull (Nullable _) = false | isNotNull _ = true datatype failure_mode = Error | None type dbms = { name : string, randomFunction : string, header : string, link : string, p_sql_type : sql_type -> string, init : {dbstring : string, prepared : (string * int) list, tables : (string * (string * sql_type) list) list, views : (string * (string * sql_type) list) list, sequences : string list} -> Print.PD.pp_desc, query : {loc : ErrorMsg.span, cols : sql_type list, doCols : ({loc : ErrorMsg.span, wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc) -> Print.PD.pp_desc} -> Print.PD.pp_desc, queryPrepared : {loc : ErrorMsg.span, id : int, query : string, inputs : sql_type list, cols : sql_type list, doCols : ({loc : ErrorMsg.span, wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc) -> Print.PD.pp_desc, nested : bool} -> Print.PD.pp_desc, dml : ErrorMsg.span * failure_mode -> Print.PD.pp_desc, dmlPrepared : {loc : ErrorMsg.span, id : int, dml : string, inputs : sql_type list, mode : failure_mode} -> Print.PD.pp_desc, nextval : {loc : ErrorMsg.span, seqName : string option, seqE : Print.PD.pp_desc} -> Print.PD.pp_desc, nextvalPrepared : {loc : ErrorMsg.span, id : int, query : string} -> Print.PD.pp_desc, setval : {loc : ErrorMsg.span, seqE : Print.PD.pp_desc, count : Print.PD.pp_desc} -> Print.PD.pp_desc, sqlifyString : string -> string, p_cast : string * sql_type -> string, p_blank : int * sql_type -> string, supportsDeleteAs : bool, supportsUpdateAs : bool, createSequence : string -> string, textKeysNeedLengths : bool, supportsNextval : bool, supportsNestedPrepared : bool, sqlPrefix : string, supportsOctetLength : bool, trueString : string, falseString : string, onlyUnion : bool, nestedRelops : bool, windowFunctions: bool } val dbmses = ref ([] : dbms list) val curDb = ref ({name = "", randomFunction = "", header = "", link = "", p_sql_type = fn _ => "", init = fn _ => Print.box [], query = fn _ => Print.box [], queryPrepared = fn _ => Print.box [], dml = fn _ => Print.box [], dmlPrepared = fn _ => Print.box [], nextval = fn _ => Print.box [], nextvalPrepared = fn _ => Print.box [], setval = fn _ => Print.box [], sqlifyString = fn s => s, p_cast = fn _ => "", p_blank = fn _ => "", supportsDeleteAs = false, supportsUpdateAs = false, createSequence = fn _ => "", textKeysNeedLengths = false, supportsNextval = false, supportsNestedPrepared = false, sqlPrefix = "", supportsOctetLength = false, trueString = "", falseString = "", onlyUnion = false, nestedRelops = false, windowFunctions = false} : dbms) fun addDbms v = dbmses := v :: !dbmses fun setDbms s = case List.find (fn db => #name db = s) (!dbmses) of NONE => raise Fail ("Unknown DBMS " ^ s) | SOME db => curDb := db fun currentDbms () = !curDb val dbstring = ref (NONE : string option) fun setDbstring so = dbstring := so fun getDbstring () = !dbstring val exe = ref (NONE : string option) fun setExe so = exe := so fun getExe () = !exe val sql = ref (NONE : string option) fun setSql so = sql := so fun getSql () = !sql val coreInline = ref 5 fun setCoreInline n = coreInline := n fun getCoreInline () = !coreInline val monoInline = ref 5 fun setMonoInline n = monoInline := n fun getMonoInline () = !monoInline val staticLinking = ref false fun setStaticLinking b = staticLinking := b fun getStaticLinking () = !staticLinking val deadlines = ref false fun setDeadlines b = deadlines := b fun getDeadlines () = !deadlines val sigFile = ref (NONE : string option) fun setSigFile v = sigFile := v fun getSigFile () = !sigFile structure SS = BinarySetFn(struct type ord_key = string val compare = String.compare end) val safeGet = ref SS.empty fun setSafeGets ls = safeGet := SS.addList (SS.empty, ls) fun isSafeGet x = SS.member (!safeGet, x) val onError = ref (NONE : (string * string list * string) option) fun setOnError x = onError := x fun getOnError () = !onError val limits = ["messages", "clients", "headers", "page", "heap", "script", "inputs", "subinputs", "cleanup", "deltas", "transactionals", "globals", "database", "time"] val limitsList = ref ([] : (string * int) list) fun addLimit (v as (name, _)) = if List.exists (fn name' => name' = name) limits then (limitsList := v :: !limitsList; if name = "time" then setDeadlines true else ()) else raise Fail ("Unknown limit category '" ^ name ^ "'") fun limits () = !limitsList val minHeap = ref 0 fun setMinHeap n = if n >= 0 then minHeap := n else raise Fail "Trying to set negative minHeap" fun getMinHeap () = !minHeap structure SS = BinarySetFn(struct type ord_key = string val compare = String.compare end) val alwaysInline = ref SS.empty fun addAlwaysInline s = alwaysInline := SS.add (!alwaysInline, s) fun checkAlwaysInline s = SS.member (!alwaysInline, s) val neverInline = ref SS.empty fun addNeverInline s = neverInline := SS.add (!neverInline, s) fun checkNeverInline s = SS.member (!neverInline, s) val noXsrfProtection = ref SS.empty fun addNoXsrfProtection s = noXsrfProtection := SS.add (!noXsrfProtection, s) fun checkNoXsrfProtection s = SS.member (!noXsrfProtection, s) val timeFormat = ref "%c" fun setTimeFormat v = timeFormat := v fun getTimeFormat () = !timeFormat fun lowercase s = case s of "" => "" | _ => str (Char.toLower (String.sub (s, 0))) ^ String.extract (s, 1, NONE) fun capitalize s = case s of "" => "" | _ => str (Char.toUpper (String.sub (s, 0))) ^ String.extract (s, 1, NONE) val allLower = CharVector.map Char.toLower val mangle = ref true fun setMangleSql x = mangle := x fun mangleSqlTable s = if #name (currentDbms ()) = "mysql" then if !mangle then "uw_" ^ allLower s else allLower s else if !mangle then "uw_" ^ capitalize s else lowercase s fun mangleSql s = if #name (currentDbms ()) = "mysql" then if !mangle then "uw_" ^ allLower s else allLower s else if !mangle then "uw_" ^ s else lowercase s fun mangleSqlCatalog s = if #name (currentDbms ()) = "mysql" then if !mangle then "uw_" ^ allLower s else allLower s else if !mangle then "uw_" ^ s else lowercase s val html5 = ref false fun setIsHtml5 b = html5 := b fun getIsHtml5 () = !html5 val less = ref false fun setLessSafeFfi b = less := b fun getLessSafeFfi () = !less structure SM = BinaryMapFn(struct type ord_key = string val compare = String.compare end) val noMimeFile = ref false fun noMime () = (TextIO.output (TextIO.stdErr, "WARNING: Error opening /etc/mime.types. Static files will be served with no suggested MIME types.\n"); noMimeFile := true; SM.empty) fun readMimeTypes () = let val inf = TextIO.openIn "/etc/mime.types" fun loop m = case TextIO.inputLine inf of NONE => m | SOME line => if size line > 0 andalso String.sub (line, 0) = #"#" then loop m else case String.tokens Char.isSpace line of typ :: exts => loop (foldl (fn (ext, m) => SM.insert (m, ext, typ)) m exts) | _ => loop m in loop SM.empty before TextIO.closeIn inf end handle IO.Io _ => noMime () | OS.SysErr _ => noMime () val mimeTypes = ref (NONE : string SM.map option) fun getMimeTypes () = case !mimeTypes of SOME m => m | NONE => let val m = readMimeTypes () in mimeTypes := SOME m; m end fun mimeTypeOf filename = case OS.Path.ext filename of NONE => (if !noMimeFile then () else TextIO.output (TextIO.stdErr, "WARNING: No extension found in filename '" ^ filename ^ "'. Header 'Content-Type' will be omitted in HTTP responses.\n"); NONE) | SOME ext => let val to = SM.find (getMimeTypes (), ext) in case to of NONE => if !noMimeFile then () else TextIO.output (TextIO.stdErr, "WARNING: No MIME type known for extension '" ^ ext ^ "'. Header 'Content-Type' will be omitted in HTTP responses.\n") | _ => (); to end val files = ref (SM.empty : (string * {Uri : string, ContentType : string option, LastModified : Time.time, Bytes : Word8Vector.vector}) SM.map) val filePath = ref "." fun setFilePath path = filePath := path fun addFile {Uri, LoadFromFilename} = let val path = OS.Path.mkAbsolute {relativeTo = !filePath, path = LoadFromFilename} in case SM.find (!files, Uri) of SOME (path', _) => if path' = path then () else ErrorMsg.error ("Two different files requested for URI " ^ Uri) | NONE => let val inf = BinIO.openIn path in files := SM.insert (!files, Uri, (path, {Uri = Uri, ContentType = mimeTypeOf path, LastModified = OS.FileSys.modTime path, Bytes = BinIO.inputAll inf})); BinIO.closeIn inf end end handle IO.Io _ => ErrorMsg.error ("Error loading file " ^ LoadFromFilename) | OS.SysErr (s, _) => ErrorMsg.error ("Error loading file " ^ LoadFromFilename ^ " (" ^ s ^ ")") fun listFiles () = map #2 (SM.listItems (!files)) end