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