diff --git a/Example/Program.fs b/Example/Program.fs index d05363e8..bb34fa1d 100644 --- a/Example/Program.fs +++ b/Example/Program.fs @@ -82,8 +82,8 @@ choose [ OK "First time") basic_auth // from here on it will require authentication GET >>= url "/events" >>= request (fun r -> EventSource.hand_shake (CounterDemo.counter_demo r)) - GET >>= browse //serves file if exists - GET >>= dir //show directory listing + GET >>= browse' //serves file if exists + GET >>= dir' //show directory listing HEAD >>= url "/head" >>= sleep 100 "Nice sleep .." POST >>= url "/upload" >>= OK "Upload successful." POST >>= url "/upload2" diff --git a/README.md b/README.md index 9e5bbe58..078047d8 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,11 @@ with the NuGet so that others can read your code's intentions easily. Don't put unnecessary parenthesis unless it makes the code more clear. +When writing functions that take some sort of 'configuration' or that you can +imagine would like to be called with a parameter which is almost always the same +value for another function body's call-site, put that parameter before +more-often-varying parameters in the function signature. + ## Testing Run Tests as a console app. Return status code = 0 means success. diff --git a/Suave/Http.fs b/Suave/Http.fs index 74357757..dfa5b2d0 100755 --- a/Suave/Http.fs +++ b/Suave/Http.fs @@ -453,7 +453,12 @@ module Http = if fs.Length > 0L then do! transfer_x conn fs } - { ctx with response = { ctx.response with status = HTTP_200; content = SocketTask (write_file file_name)}} |> succeed + { ctx with + response = + { ctx.response with + status = HTTP_200 + content = SocketTask (write_file file_name) } } + |> succeed let file file_name = resource @@ -475,16 +480,27 @@ module Http = else raise <| Exception("File canonalization issue.") let browse_file file_name = - fun ({request = r; runtime = q} as h) -> file (local_file file_name q.home_directory) h + fun ({request = r; runtime = q} as h) -> + file (local_file file_name q.home_directory) h + + let browse root_path : WebPart = + warbler (fun { request = r; runtime = { logger = l } } -> + Log.verbose l + "Suave.Http.Files.browse" + Log.TraceHeader.empty + (sprintf "Files.browse trying file (local_file url:'%s' root:'%s')" + r.url root_path) + file (local_file r.url root_path)) - let browse : WebPart = warbler (fun {request = r; runtime = q } -> file (local_file r.url q.home_directory)) + let browse' : WebPart = + warbler (fun { runtime = q } -> browse q.home_directory) - let dir (ctx : HttpContext) = + let dir root_path (ctx : HttpContext) = let req = ctx.request let url = req.url - let dirname = local_file url (ctx.runtime.home_directory) + let dirname = local_file url root_path let result = new StringBuilder() let filesize (x : FileSystemInfo) = @@ -506,6 +522,9 @@ module Http = OK (result.ToString()) ctx else fail + let dir' ctx = + dir ctx.runtime.home_directory ctx + module Embedded = open System diff --git a/Suave/Http.fsi b/Suave/Http.fsi index fa0d768b..9cd07579 100755 --- a/Suave/Http.fsi +++ b/Suave/Http.fsi @@ -1309,6 +1309,9 @@ module Http = /// Responses to this method are not cacheable. val OPTIONS : WebPart + /// The files module can be used to serve from the file system. It encapsulates + /// common patterns like verifying that back-symlinks or keywords aren't used + /// to gain access outside the intended folder. module Files = /// @@ -1318,60 +1321,63 @@ module Http = /// /// /// - val send_file : filename:string -> compression:bool -> WebPart + val send_file : file_name:string -> compression:bool -> WebPart /// /// Send the file by the filename given. Will search relative to the current directory for /// the file path, unless you pass it a file with a slash at the start of its name, in which /// case it will search the root of the file system that is hosting the current directory. /// Will also set the MIME type based on the file extension. - /// - /// /// /// /// - val file : filename:string -> WebPart + val file : file_name:string -> WebPart /// /// Format a string with a local file path given a file name 'fileName'. You should /// use this helper method to find the current directory and concatenate that current /// directory to the filename which should be absolute and start with a path separator. - /// - /// /// /// + /// The current implementation doesn't take kindly to relative paths. /// - val local_file : fileName:string -> rootDir:string -> string + val local_file : file_name:string -> root_path:string -> string /// /// 'browse' the file given as the filename, by sending it to the browser with a /// MIME-type/Content-Type header based on its extension. Will service from the /// current directory. - /// - /// /// - /// - /// - val browse_file : filename:string -> WebPart + val browse_file : file_name:string -> WebPart /// /// 'browse' the file in the sense that the contents of the file are sent based on the - /// request's Url property. Will serve from the current directory. - /// - /// + /// request's Url property. Will serve from the passed root path/directory. /// /// + /// The current implementation doesn't take kindly to relative paths. /// - val browse : WebPart + val browse : root_path:string -> WebPart /// - /// Serve a 'file browser' for a directory - /// - /// + /// 'browse' the file in the sense that the contents of the file are sent based on the + /// request's Url property. Will serve from the current as configured in directory. + /// Suave's runtime. + /// + val browse' : WebPart + + /// + /// Serve a 'file browser' for a root_path /// /// + /// The current implementation doesn't take kindly to relative paths. /// - val dir : WebPart + val dir : root_path:string -> WebPart + + /// + /// Serve a 'file browser' for the current directory + /// + val dir' : WebPart module Embedded = diff --git a/tools/http-headers-status-v3.png b/tools/http-headers-status-v3.png deleted file mode 100644 index 23131606..00000000 Binary files a/tools/http-headers-status-v3.png and /dev/null differ