From 10f7ae1d0d0c7f694be098bfeb91a1d42348baf3 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 15:55:30 +0100
Subject: [PATCH 01/28] new directory using webdav

---
 assets/embed/public/js/application.js |  5 ++--
 filemanager.go                        | 37 +--------------------------
 2 files changed, 3 insertions(+), 39 deletions(-)

diff --git a/assets/embed/public/js/application.js b/assets/embed/public/js/application.js
index 58145e82..30246b62 100644
--- a/assets/embed/public/js/application.js
+++ b/assets/embed/public/js/application.js
@@ -430,13 +430,12 @@ var newDirEvent = function(event) {
         let button = document.getElementById('new');
         let html = button.changeToLoading();
         let request = new XMLHttpRequest();
-        request.open("POST", window.location);
+        request.open("MKCOL", toWebDavURL(window.location.pathname + document.getElementById('newdir').value + "/"));
         request.setRequestHeader('Token', token);
-        request.setRequestHeader('Filename', document.getElementById('newdir').value);
         request.send();
         request.onreadystatechange = function() {
             if (request.readyState == 4) {
-                button.changeToDone((request.status != 200), html);
+                button.changeToDone((request.status != 201), html);
                 reloadListing(() => {
                     addNewDirEvents();
                 });
diff --git a/filemanager.go b/filemanager.go
index 97811360..6ed614c5 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -10,7 +10,6 @@ package filemanager
 import (
 	e "errors"
 	"io"
-	"io/ioutil"
 	"log"
 	"mime/multipart"
 	"net/http"
@@ -179,8 +178,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 					return command(w, r, c, user)
 				}
 
-				// Creates a new folder.
-				return newDirectory(w, r, c)
+				fallthrough
 			default:
 				return http.StatusNotImplemented, nil
 			}
@@ -234,39 +232,6 @@ func upload(w http.ResponseWriter, r *http.Request, c *config.Config) (int, erro
 	return http.StatusOK, nil
 }
 
-// newDirectory makes a new directory
-func newDirectory(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
-	filename := r.Header.Get("Filename")
-
-	if filename == "" {
-		return http.StatusBadRequest, nil
-	}
-
-	path := strings.Replace(r.URL.Path, c.BaseURL, c.PathScope, 1) + filename
-	path = filepath.Clean(path)
-	extension := filepath.Ext(path)
-
-	var err error
-
-	if extension == "" {
-		err = os.MkdirAll(path, 0775)
-	} else {
-		err = ioutil.WriteFile(path, []byte(""), 0775)
-	}
-
-	if err != nil {
-		switch {
-		case os.IsPermission(err):
-			return http.StatusForbidden, err
-		case os.IsExist(err):
-			return http.StatusConflict, err
-		default:
-			return http.StatusInternalServerError, err
-		}
-	}
-	return http.StatusCreated, nil
-}
-
 // command handles the requests for VCS related commands: git, svn and mercurial
 func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	command := strings.Split(r.Header.Get("command"), " ")

From 59f5109617d318b77750a11525023c8439e5299d Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 16:17:01 +0100
Subject: [PATCH 02/28] save file using webdav

---
 assets/embed/public/js/application.js |  5 ++-
 directory/file.go                     |  2 +
 directory/update.go                   | 63 +++++++++++++++------------
 filemanager.go                        | 20 ++++-----
 4 files changed, 47 insertions(+), 43 deletions(-)

diff --git a/assets/embed/public/js/application.js b/assets/embed/public/js/application.js
index 30246b62..e326f9b4 100644
--- a/assets/embed/public/js/application.js
+++ b/assets/embed/public/js/application.js
@@ -417,6 +417,7 @@ var addNewDirEvents = function() {
 
 // Handles the new directory event
 var newDirEvent = function(event) {
+    // TODO: create new dir button and new file button
     if (event.keyCode == 27) {
         document.getElementById('newdir').classList.toggle('enabled');
         setTimeout(() => {
@@ -831,13 +832,13 @@ document.addEventListener("editor", (event) => {
         let data = form2js(document.querySelector('form'));
         let html = button.changeToLoading();
         let request = new XMLHttpRequest();
-        request.open("PUT", window.location);
+        request.open("PUT", toWebDavURL(window.location.pathname));
         request.setRequestHeader('Kind', kind);
         request.setRequestHeader('Token', token);
         request.send(JSON.stringify(data));
         request.onreadystatechange = function() {
             if (request.readyState == 4) {
-                button.changeToDone((request.status != 200), html);
+                button.changeToDone((request.status != 201), html);
             }
         }
     }
diff --git a/directory/file.go b/directory/file.go
index a68936f1..240c809a 100644
--- a/directory/file.go
+++ b/directory/file.go
@@ -144,6 +144,8 @@ func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config
 		}
 
 		page.Info.Data = editor
+
+		// TODO: if serve Single File finds an error while parsing, show the raw content to edit instead of giving 500
 		return page.PrintAsHTML(w, "frontmatter", "editor")
 	}
 
diff --git a/directory/update.go b/directory/update.go
index 7a0f5c3a..34d1ba15 100644
--- a/directory/update.go
+++ b/directory/update.go
@@ -16,48 +16,53 @@ import (
 
 // Update is used to update a file that was edited
 func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+	// TODO: review this
+
 	var data map[string]interface{}
 	kind := r.Header.Get("kind")
 
-	if kind == "" {
-		return http.StatusBadRequest, nil
-	}
-
-	// Get the JSON information
-	rawBuffer := new(bytes.Buffer)
-	rawBuffer.ReadFrom(r.Body)
-	err := json.Unmarshal(rawBuffer.Bytes(), &data)
-
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-
 	var file []byte
 	var code int
 
-	switch kind {
-	case "frontmatter-only":
-		if file, code, err = ParseFrontMatterOnlyFile(data, i.Name); err != nil {
+	rawBuffer := new(bytes.Buffer)
+	rawBuffer.ReadFrom(r.Body)
+
+	if kind == "" {
+		file = rawBuffer.Bytes()
+	} else {
+		err := json.Unmarshal(rawBuffer.Bytes(), &data)
+
+		if err != nil {
 			return http.StatusInternalServerError, err
 		}
-	case "content-only":
-		mainContent := data["content"].(string)
-		mainContent = strings.TrimSpace(mainContent)
-		file = []byte(mainContent)
-	case "complete":
-		if file, code, err = ParseCompleteFile(data, i.Name, u.FrontMatter); err != nil {
-			return http.StatusInternalServerError, err
+
+		switch kind {
+		case "frontmatter-only":
+			if file, code, err = ParseFrontMatterOnlyFile(data, i.Name); err != nil {
+				return http.StatusInternalServerError, err
+			}
+		case "content-only":
+			mainContent := data["content"].(string)
+			mainContent = strings.TrimSpace(mainContent)
+			file = []byte(mainContent)
+		case "complete":
+			if file, code, err = ParseCompleteFile(data, i.Name, u.FrontMatter); err != nil {
+				return http.StatusInternalServerError, err
+			}
+		default:
+			return http.StatusBadRequest, nil
 		}
-	default:
-		return http.StatusBadRequest, nil
 	}
 
+	// Overwrite the Body
+	r.Body = ioutil.NopCloser(bytes.NewReader(file))
+
 	// Write the file
-	err = ioutil.WriteFile(i.Path, file, 0666)
+	// err = ioutil.WriteFile(i.Path, file, 0666)
 
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
+	//if err != nil {
+	//return http.StatusInternalServerError, err
+	//	}
 
 	return code, nil
 }
diff --git a/filemanager.go b/filemanager.go
index 6ed614c5..3bf02019 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -57,6 +57,13 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 			}
 
 			if c.WebDav && strings.HasPrefix(r.URL.Path, c.WebDavURL) {
+				if r.Method == http.MethodPut {
+					_, err = fi.Update(w, r, c, user)
+					if err != nil {
+						return http.StatusInternalServerError, err
+					}
+				}
+
 				//url := strings.TrimPrefix(r.URL.Path, c.WebDavURL)
 
 				/*
@@ -143,17 +150,6 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 					return errors.PrintHTML(w, code, err)
 				}
 				return code, err
-			case http.MethodPut:
-				if fi.IsDir {
-					return http.StatusNotAcceptable, nil
-				}
-
-				if !user.AllowEdit {
-					return http.StatusForbidden, nil
-				}
-
-				// Update a file.
-				return fi.Update(w, r, c, user)
 			case http.MethodPost:
 				// Upload a new file.
 				if r.Header.Get("Upload") == "true" {
@@ -166,7 +162,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 				// Search and git commands.
 				if r.Header.Get("Search") == "true" {
-					// TODO: search commands.
+					// TODO: search commands. USE PROPFIND?
 				}
 
 				// VCS commands.

From 3683c4c06a1a0b733f02644d419a095fa2c5c136 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 16:42:48 +0100
Subject: [PATCH 03/28] Always using webdav

---
 assets/embed/public/css/styles.css    |  4 +-
 assets/embed/public/js/application.js |  4 +-
 assets/embed/templates/listing.tmpl   |  2 +-
 config/config.go                      | 22 +++++------
 filemanager.go                        | 56 ++++++++++++++-------------
 5 files changed, 46 insertions(+), 42 deletions(-)

diff --git a/assets/embed/public/css/styles.css b/assets/embed/public/css/styles.css
index 7fe9d964..bb21074c 100644
--- a/assets/embed/public/css/styles.css
+++ b/assets/embed/public/css/styles.css
@@ -774,7 +774,7 @@ header .action span {
     border: 0;
     box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);
     padding: .5em;
-    width: 10em;
+    width: 22em;
     border-radius: .2em;
 }
 
@@ -1166,4 +1166,4 @@ i.spin {
         column-count: 1;
         column-gap: 0;
     }
-}
\ No newline at end of file
+}
diff --git a/assets/embed/public/js/application.js b/assets/embed/public/js/application.js
index e326f9b4..ee333a21 100644
--- a/assets/embed/public/js/application.js
+++ b/assets/embed/public/js/application.js
@@ -431,7 +431,9 @@ var newDirEvent = function(event) {
         let button = document.getElementById('new');
         let html = button.changeToLoading();
         let request = new XMLHttpRequest();
-        request.open("MKCOL", toWebDavURL(window.location.pathname + document.getElementById('newdir').value + "/"));
+        let name = document.getElementById('newdir').value;
+
+        request.open((name.endsWith("/") ? "MKCOL" : "PUT"), toWebDavURL(window.location.pathname + name));
         request.setRequestHeader('Token', token);
         request.send();
         request.onreadystatechange = function() {
diff --git a/assets/embed/templates/listing.tmpl b/assets/embed/templates/listing.tmpl
index 70f443d7..c03f5f34 100644
--- a/assets/embed/templates/listing.tmpl
+++ b/assets/embed/templates/listing.tmpl
@@ -38,7 +38,7 @@
 {{ end }}
 
 {{ if .User.AllowNew }}
-<input id="newdir" type="text" placeholder="Name...">
+<input id="newdir" type="text" placeholder="Name. End with a trailing slash to create a dir.">
 <div class="floating">
     <div class="action" id="new">
         <i class="material-icons" title="New file or directory. If you don't write an extension, a directory will be created.">add</i>
diff --git a/config/config.go b/config/config.go
index c5f0145e..952cb134 100644
--- a/config/config.go
+++ b/config/config.go
@@ -69,6 +69,7 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 		cfg.AllowEdit = true
 		cfg.AllowNew = true
 		cfg.Commands = []string{"git", "svn", "hg"}
+		cfg.WebDav = true
 		cfg.Rules = []*Rule{&Rule{
 			Regex:  true,
 			Allow:  false,
@@ -85,6 +86,7 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 		cfg.BaseURL = strings.TrimPrefix(cfg.BaseURL, "/")
 		cfg.BaseURL = strings.TrimSuffix(cfg.BaseURL, "/")
 		cfg.BaseURL = "/" + cfg.BaseURL
+		cfg.WebDavURL = cfg.BaseURL + "webdav"
 
 		if cfg.BaseURL == "/" {
 			cfg.BaseURL = ""
@@ -105,23 +107,15 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 					return configs, c.Err("frontmatter type not supported")
 				}
 			case "webdav":
-				cfg.WebDav = true
-
-				prefix := "webdav"
-				if c.NextArg() {
-					prefix = c.Val()
+				if !c.NextArg() {
+					return configs, c.ArgErr()
 				}
 
+				prefix := c.Val()
 				prefix = strings.TrimPrefix(prefix, "/")
 				prefix = strings.TrimSuffix(prefix, "/")
 				prefix = cfg.BaseURL + "/" + prefix
-
 				cfg.WebDavURL = prefix
-				cfg.WebDavHandler = &webdav.Handler{
-					Prefix:     prefix,
-					FileSystem: webdav.Dir(cfg.PathScope),
-					LockSystem: webdav.NewMemLS(),
-				}
 			case "show":
 				if !c.NextArg() {
 					return configs, c.ArgErr()
@@ -240,6 +234,12 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 			}
 		}
 
+		cfg.WebDavHandler = &webdav.Handler{
+			Prefix:     cfg.WebDavURL,
+			FileSystem: webdav.Dir(cfg.PathScope),
+			LockSystem: webdav.NewMemLS(),
+		}
+
 		caddyConf := httpserver.GetConfig(c)
 		cfg.AbsoluteURL = strings.TrimSuffix(caddyConf.Addr.Path, "/") + "/" + cfg.BaseURL
 		cfg.AbsoluteURL = strings.Replace(cfg.AbsoluteURL, "//", "/", -1)
diff --git a/filemanager.go b/filemanager.go
index 3bf02019..2c0c5009 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -56,7 +56,24 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				user = c.User
 			}
 
-			if c.WebDav && strings.HasPrefix(r.URL.Path, c.WebDavURL) {
+			if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
+				url := strings.TrimPrefix(r.URL.Path, c.WebDavURL)
+
+				if !user.Allowed(url) {
+					return http.StatusForbidden, nil
+				}
+
+				switch r.Method {
+				case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
+					if !user.AllowEdit {
+						return http.StatusForbidden, nil
+					}
+				case "MKCOL", "COPY":
+					if !user.AllowNew {
+						return http.StatusForbidden, nil
+					}
+				}
+
 				if r.Method == http.MethodPut {
 					_, err = fi.Update(w, r, c, user)
 					if err != nil {
@@ -64,24 +81,6 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 					}
 				}
 
-				//url := strings.TrimPrefix(r.URL.Path, c.WebDavURL)
-
-				/*
-					if !user.Allowed(url) {
-						return http.StatusForbidden, nil
-					}
-
-					switch r.Method {
-					case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
-						if !user.AllowEdit {
-							return http.StatusForbidden, nil
-						}
-					case "MKCOL", "COPY":
-						if !user.AllowNew {
-							return http.StatusForbidden, nil
-						}
-					} */
-
 				c.WebDavHandler.ServeHTTP(w, r)
 				return 0, nil
 			}
@@ -122,9 +121,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				}
 			}
 
-			// Route the request depending on the HTTP Method.
-			switch r.Method {
-			case http.MethodGet:
+			if r.Method == http.MethodGet {
 				// Read and show directory or file.
 				if serveAssets {
 					return assets.Serve(w, r, c)
@@ -136,13 +133,18 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				if !fi.IsDir {
 					query := r.URL.Query()
 					if val, ok := query["raw"]; ok && val[0] == "true" {
+						// TODO: change URL to webdav and continue as webdav
 						return fi.ServeRawFile(w, r, c)
 					}
 
 					if val, ok := query["download"]; ok && val[0] == "true" {
 						w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name)
+						// TODO: change URL to webdav and continue as webdav
 						return fi.ServeRawFile(w, r, c)
+
 					}
+
+					// c.WebDavHandler.ServeHTTP(w, r)
 				}
 
 				code, err := fi.ServeAsHTML(w, r, c, user)
@@ -150,7 +152,9 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 					return errors.PrintHTML(w, code, err)
 				}
 				return code, err
-			case http.MethodPost:
+			}
+
+			if r.Method == http.MethodPost {
 				// Upload a new file.
 				if r.Header.Get("Upload") == "true" {
 					if !user.AllowNew {
@@ -173,11 +177,9 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 					return command(w, r, c, user)
 				}
-
-				fallthrough
-			default:
-				return http.StatusNotImplemented, nil
 			}
+
+			return http.StatusNotImplemented, nil
 		}
 	}
 

From ccc539c592d77e086a3859f0829521b87f5b916f Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 17:00:12 +0100
Subject: [PATCH 04/28] simplify router

---
 directory/file.go | 19 ++-------------
 filemanager.go    | 61 +++++++++++++++++++----------------------------
 2 files changed, 27 insertions(+), 53 deletions(-)

diff --git a/directory/file.go b/directory/file.go
index 240c809a..e18b83d6 100644
--- a/directory/file.go
+++ b/directory/file.go
@@ -122,7 +122,8 @@ func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config
 	}
 
 	if i.Type == "blob" {
-		return i.ServeRawFile(w, r, c)
+		http.Redirect(w, r, c.AddrPath+r.URL.Path+"?download=true", http.StatusTemporaryRedirect)
+		return 0, nil
 	}
 
 	page := &p.Page{
@@ -274,22 +275,6 @@ func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *c
 	}
 }
 
-// ServeRawFile serves raw files
-func (i *Info) ServeRawFile(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
-	err := i.GetExtendedInfo()
-	if err != nil {
-		return errors.ToHTTPCode(err), err
-	}
-
-	if i.Type != "text" {
-		i.Read()
-	}
-
-	w.Header().Set("Content-Type", i.Mimetype)
-	w.Write([]byte(i.Content))
-	return 200, nil
-}
-
 // SimplifyMimeType returns the base type of a file
 func SimplifyMimeType(name string) string {
 	if strings.HasPrefix(name, "video") {
diff --git a/filemanager.go b/filemanager.go
index 2c0c5009..c4f8364a 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -56,13 +56,24 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				user = c.User
 			}
 
-			if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
-				url := strings.TrimPrefix(r.URL.Path, c.WebDavURL)
-
-				if !user.Allowed(url) {
-					return http.StatusForbidden, nil
+			// TODO: make allow and block rules relative to baseurl and webdav
+			// Checks if the user has permission to access the current directory.
+			if !user.Allowed(r.URL.Path) {
+				if r.Method == http.MethodGet {
+					return errors.PrintHTML(w, http.StatusForbidden, e.New("You don't have permission to access this page."))
 				}
 
+				return http.StatusForbidden, nil
+			}
+
+			// Security measures against CSRF attacks.
+			if r.Method != http.MethodGet {
+				if !c.CheckToken(r) {
+					return http.StatusForbidden, nil
+				}
+			}
+
+			if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
 				switch r.Method {
 				case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
 					if !user.AllowEdit {
@@ -85,18 +96,11 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				return 0, nil
 			}
 
-			// Checks if the user has permission to access the current directory.
-			if !user.Allowed(r.URL.Path) {
-				if r.Method == http.MethodGet {
-					return errors.PrintHTML(w, http.StatusForbidden, e.New("You don't have permission to access this page."))
-				}
-
-				return http.StatusForbidden, nil
+			if r.Method == http.MethodGet && serveAssets {
+				return assets.Serve(w, r, c)
 			}
 
-			// If this request is neither to server assets, nor to upload/create
-			// a new file or directory.
-			if r.Method != http.MethodPost && !serveAssets {
+			if r.Method == http.MethodGet {
 				// Gets the information of the directory/file
 				fi, code, err = directory.GetInfo(r.URL, c, user)
 				if err != nil {
@@ -112,20 +116,6 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 					http.Redirect(w, r, c.AddrPath+r.URL.Path+"/", http.StatusTemporaryRedirect)
 					return 0, nil
 				}
-			}
-
-			// Security measures against CSRF attacks.
-			if r.Method != http.MethodGet {
-				if !c.CheckToken(r) {
-					return http.StatusForbidden, nil
-				}
-			}
-
-			if r.Method == http.MethodGet {
-				// Read and show directory or file.
-				if serveAssets {
-					return assets.Serve(w, r, c)
-				}
 
 				// Generate anti security token.
 				c.GenerateToken()
@@ -133,18 +123,17 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				if !fi.IsDir {
 					query := r.URL.Query()
 					if val, ok := query["raw"]; ok && val[0] == "true" {
-						// TODO: change URL to webdav and continue as webdav
-						return fi.ServeRawFile(w, r, c)
+						r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
+						c.WebDavHandler.ServeHTTP(w, r)
+						return 0, nil
 					}
 
 					if val, ok := query["download"]; ok && val[0] == "true" {
 						w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name)
-						// TODO: change URL to webdav and continue as webdav
-						return fi.ServeRawFile(w, r, c)
-
+						r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
+						c.WebDavHandler.ServeHTTP(w, r)
+						return 0, nil
 					}
-
-					// c.WebDavHandler.ServeHTTP(w, r)
 				}
 
 				code, err := fi.ServeAsHTML(w, r, c, user)

From 0a755ec954626b8581b9dd21597b7acdc0be2381 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 17:24:54 +0100
Subject: [PATCH 05/28] upload is now webdav

---
 assets/embed/public/js/application.js | 26 +++++-----
 filemanager.go                        | 71 ++++-----------------------
 2 files changed, 21 insertions(+), 76 deletions(-)

diff --git a/assets/embed/public/js/application.js b/assets/embed/public/js/application.js
index ee333a21..8dbe38b3 100644
--- a/assets/embed/public/js/application.js
+++ b/assets/embed/public/js/application.js
@@ -299,27 +299,25 @@ var renameEvent = function(event) {
 var handleFiles = function(files) {
     let button = document.getElementById("upload");
     let html = button.changeToLoading();
-    let data = new FormData();
 
     for (let i = 0; i < files.length; i++) {
-        data.append(files[i].name, files[i]);
-    }
+        let request = new XMLHttpRequest();
+        request.open('PUT', toWebDavURL(window.location.pathname + files[i].name));
+        request.setRequestHeader('Token', token);
+        request.send(files[i]);
+        request.onreadystatechange = function() {
+            if (request.readyState == 4) {
+                if (request.status == 201) {
+                    reloadListing();
+                }
 
-    let request = new XMLHttpRequest();
-    request.open('POST', window.location.pathname);
-    request.setRequestHeader("Upload", "true");
-    request.setRequestHeader('Token', token);
-    request.send(data);
-    request.onreadystatechange = function() {
-        if (request.readyState == 4) {
-            if (request.status == 200) {
-                reloadListing();
+                button.changeToDone((request.status != 201), html);
             }
-
-            button.changeToDone((request.status != 200), html);
         }
     }
 
+
+
     return false;
 }
 
diff --git a/filemanager.go b/filemanager.go
index c4f8364a..f1dfe856 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -8,12 +8,8 @@
 package filemanager
 
 import (
-	e "errors"
-	"io"
-	"log"
-	"mime/multipart"
+	"fmt"
 	"net/http"
-	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
@@ -58,7 +54,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 			// TODO: make allow and block rules relative to baseurl and webdav
 			// Checks if the user has permission to access the current directory.
-			if !user.Allowed(r.URL.Path) {
+			/*if !user.Allowed(r.URL.Path) {
 				if r.Method == http.MethodGet {
 					return errors.PrintHTML(w, http.StatusForbidden, e.New("You don't have permission to access this page."))
 				}
@@ -66,14 +62,17 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				return http.StatusForbidden, nil
 			}
 
+			// TODO: How to exclude web dav clients? :/
 			// Security measures against CSRF attacks.
 			if r.Method != http.MethodGet {
 				if !c.CheckToken(r) {
 					return http.StatusForbidden, nil
 				}
-			}
+			} */
 
 			if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
+				fmt.Println("e")
+
 				switch r.Method {
 				case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
 					if !user.AllowEdit {
@@ -144,19 +143,11 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 			}
 
 			if r.Method == http.MethodPost {
-				// Upload a new file.
-				if r.Header.Get("Upload") == "true" {
-					if !user.AllowNew {
-						return http.StatusUnauthorized, nil
-					}
-
-					return upload(w, r, c)
-				}
-
+				/* TODO: search commands. USE PROPFIND?
 				// Search and git commands.
 				if r.Header.Get("Search") == "true" {
-					// TODO: search commands. USE PROPFIND?
-				}
+
+				} */
 
 				// VCS commands.
 				if r.Header.Get("Command") != "" {
@@ -175,50 +166,6 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 	return f.Next.ServeHTTP(w, r)
 }
 
-// upload is used to handle the upload requests to the server
-func upload(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
-	// Parse the multipart form in the request
-	err := r.ParseMultipartForm(100000)
-	if err != nil {
-		log.Println(err)
-		return http.StatusInternalServerError, err
-	}
-
-	// For each file header in the multipart form
-	for _, headers := range r.MultipartForm.File {
-		// Handle each file
-		for _, header := range headers {
-			// Open the first file
-			var src multipart.File
-			if src, err = header.Open(); nil != err {
-				return http.StatusInternalServerError, err
-			}
-
-			filename := strings.Replace(r.URL.Path, c.BaseURL, c.PathScope, 1)
-			filename = filename + header.Filename
-			filename = filepath.Clean(filename)
-
-			// Create the file
-			var dst *os.File
-			if dst, err = os.Create(filename); nil != err {
-				if os.IsExist(err) {
-					return http.StatusConflict, err
-				}
-				return http.StatusInternalServerError, err
-			}
-
-			// Copy the file content
-			if _, err = io.Copy(dst, src); nil != err {
-				return http.StatusInternalServerError, err
-			}
-
-			defer dst.Close()
-		}
-	}
-
-	return http.StatusOK, nil
-}
-
 // command handles the requests for VCS related commands: git, svn and mercurial
 func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	command := strings.Split(r.Header.Get("command"), " ")

From 22e0ad0831ab92c513968947f4b02af19ba18b8a Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 17:56:35 +0100
Subject: [PATCH 06/28] update

---
 assets/embed/public/js/application.js |  4 +-
 directory/editor.go                   | 37 ++++++++-------
 directory/file.go                     |  2 -
 directory/update.go                   | 67 ++++++++++++---------------
 filemanager.go                        | 57 ++++++++++++-----------
 5 files changed, 81 insertions(+), 86 deletions(-)

diff --git a/assets/embed/public/js/application.js b/assets/embed/public/js/application.js
index 8dbe38b3..a4e2b86f 100644
--- a/assets/embed/public/js/application.js
+++ b/assets/embed/public/js/application.js
@@ -1,5 +1,7 @@
 'use strict';
 
+// TODO: way to get the webdav url
+
 var tempID = "_fm_internal_temporary_id"
 var selectedItems = [];
 var token = "";
@@ -316,8 +318,6 @@ var handleFiles = function(files) {
         }
     }
 
-
-
     return false;
 }
 
diff --git a/directory/editor.go b/directory/editor.go
index 7ccb4ddd..b6b43371 100644
--- a/directory/editor.go
+++ b/directory/editor.go
@@ -42,23 +42,26 @@ func (i *Info) GetEditor() (*Editor, error) {
 	// Handle the content depending on the file extension
 	switch editor.Mode {
 	case "markdown", "asciidoc", "rst":
-		if HasFrontMatterRune(i.Raw) {
-			// Starts a new buffer and parses the file using Hugo's functions
-			buffer := bytes.NewBuffer(i.Raw)
-			page, err = parser.ReadFrom(buffer)
-			if err != nil {
-				return editor, err
-			}
-
-			// Parses the page content and the frontmatter
-			editor.Content = strings.TrimSpace(string(page.Content()))
-			editor.FrontMatter, _, err = frontmatter.Pretty(page.FrontMatter())
-			editor.Class = "complete"
-		} else {
-			// The editor will handle only content
+		if !HasFrontMatterRune(i.Raw) {
 			editor.Class = "content-only"
 			editor.Content = i.Content
+			break
 		}
+
+		// Starts a new buffer and parses the file using Hugo's functions
+		buffer := bytes.NewBuffer(i.Raw)
+		page, err = parser.ReadFrom(buffer)
+		editor.Class = "complete"
+
+		if err != nil {
+			editor.Class = "content-only"
+			editor.Content = i.Content
+			break
+		}
+
+		// Parses the page content and the frontmatter
+		editor.Content = strings.TrimSpace(string(page.Content()))
+		editor.FrontMatter, _, err = frontmatter.Pretty(page.FrontMatter())
 	case "json", "toml", "yaml":
 		// Defines the class and declares an error
 		editor.Class = "frontmatter-only"
@@ -72,13 +75,15 @@ func (i *Info) GetEditor() (*Editor, error) {
 
 		// Check if there were any errors
 		if err != nil {
-			return editor, err
+			editor.Class = "content-only"
+			editor.Content = i.Content
+			break
 		}
 	default:
-		// The editor will handle only content
 		editor.Class = "content-only"
 		editor.Content = i.Content
 	}
+
 	return editor, nil
 }
 
diff --git a/directory/file.go b/directory/file.go
index e18b83d6..300691da 100644
--- a/directory/file.go
+++ b/directory/file.go
@@ -145,8 +145,6 @@ func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config
 		}
 
 		page.Info.Data = editor
-
-		// TODO: if serve Single File finds an error while parsing, show the raw content to edit instead of giving 500
 		return page.PrintAsHTML(w, "frontmatter", "editor")
 	}
 
diff --git a/directory/update.go b/directory/update.go
index 34d1ba15..10483769 100644
--- a/directory/update.go
+++ b/directory/update.go
@@ -16,54 +16,45 @@ import (
 
 // Update is used to update a file that was edited
 func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	// TODO: review this
+	var (
+		data      map[string]interface{}
+		file      []byte
+		code      int
+		err       error
+		kind      string
+		rawBuffer = new(bytes.Buffer)
+	)
 
-	var data map[string]interface{}
-	kind := r.Header.Get("kind")
-
-	var file []byte
-	var code int
-
-	rawBuffer := new(bytes.Buffer)
+	kind = r.Header.Get("kind")
 	rawBuffer.ReadFrom(r.Body)
 
-	if kind == "" {
-		file = rawBuffer.Bytes()
-	} else {
-		err := json.Unmarshal(rawBuffer.Bytes(), &data)
+	if kind != "" {
+		err = json.Unmarshal(rawBuffer.Bytes(), &data)
 
 		if err != nil {
 			return http.StatusInternalServerError, err
 		}
-
-		switch kind {
-		case "frontmatter-only":
-			if file, code, err = ParseFrontMatterOnlyFile(data, i.Name); err != nil {
-				return http.StatusInternalServerError, err
-			}
-		case "content-only":
-			mainContent := data["content"].(string)
-			mainContent = strings.TrimSpace(mainContent)
-			file = []byte(mainContent)
-		case "complete":
-			if file, code, err = ParseCompleteFile(data, i.Name, u.FrontMatter); err != nil {
-				return http.StatusInternalServerError, err
-			}
-		default:
-			return http.StatusBadRequest, nil
-		}
 	}
 
-	// Overwrite the Body
+	switch kind {
+	case "frontmatter-only":
+		if file, code, err = ParseFrontMatterOnlyFile(data, i.Name); err != nil {
+			return http.StatusInternalServerError, err
+		}
+	case "content-only":
+		mainContent := data["content"].(string)
+		mainContent = strings.TrimSpace(mainContent)
+		file = []byte(mainContent)
+	case "complete":
+		if file, code, err = ParseCompleteFile(data, i.Name, u.FrontMatter); err != nil {
+			return http.StatusInternalServerError, err
+		}
+	default:
+		file = rawBuffer.Bytes()
+	}
+
+	// Overwrite the request Body
 	r.Body = ioutil.NopCloser(bytes.NewReader(file))
-
-	// Write the file
-	// err = ioutil.WriteFile(i.Path, file, 0666)
-
-	//if err != nil {
-	//return http.StatusInternalServerError, err
-	//	}
-
 	return code, nil
 }
 
diff --git a/filemanager.go b/filemanager.go
index f1dfe856..3efc3cd6 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -8,7 +8,7 @@
 package filemanager
 
 import (
-	"fmt"
+	e "errors"
 	"net/http"
 	"os/exec"
 	"path/filepath"
@@ -32,18 +32,21 @@ type FileManager struct {
 // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
 func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 	var (
-		c           *config.Config
-		fi          *directory.Info
-		code        int
-		err         error
-		serveAssets bool
-		user        *config.User
+		c    *config.Config
+		fi   *directory.Info
+		code int
+		err  error
+		user *config.User
 	)
 
 	for i := range f.Configs {
 		if httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
 			c = &f.Configs[i]
-			serveAssets = httpserver.Path(r.URL.Path).Matches(c.BaseURL + assets.BaseURL)
+
+			if r.Method == http.MethodGet && httpserver.Path(r.URL.Path).Matches(c.BaseURL+assets.BaseURL) {
+				return assets.Serve(w, r, c)
+			}
+
 			username, _, _ := r.BasicAuth()
 
 			if _, ok := c.Users[username]; ok {
@@ -52,26 +55,10 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				user = c.User
 			}
 
-			// TODO: make allow and block rules relative to baseurl and webdav
-			// Checks if the user has permission to access the current directory.
-			/*if !user.Allowed(r.URL.Path) {
-				if r.Method == http.MethodGet {
-					return errors.PrintHTML(w, http.StatusForbidden, e.New("You don't have permission to access this page."))
-				}
-
-				return http.StatusForbidden, nil
-			}
-
-			// TODO: How to exclude web dav clients? :/
-			// Security measures against CSRF attacks.
-			if r.Method != http.MethodGet {
-				if !c.CheckToken(r) {
+			if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
+				if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.WebDavURL)) {
 					return http.StatusForbidden, nil
 				}
-			} */
-
-			if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
-				fmt.Println("e")
 
 				switch r.Method {
 				case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
@@ -95,8 +82,16 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				return 0, nil
 			}
 
-			if r.Method == http.MethodGet && serveAssets {
-				return assets.Serve(w, r, c)
+			if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.BaseURL)) {
+				if r.Method == http.MethodGet {
+					return errors.PrintHTML(
+						w,
+						http.StatusForbidden,
+						e.New("You don't have permission to access this page."),
+					)
+				}
+
+				return http.StatusForbidden, nil
 			}
 
 			if r.Method == http.MethodGet {
@@ -143,6 +138,12 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 			}
 
 			if r.Method == http.MethodPost {
+				// TODO: How to exclude web dav clients? :/
+				// Security measures against CSRF attacks.
+				if !c.CheckToken(r) {
+					return http.StatusForbidden, nil
+				}
+
 				/* TODO: search commands. USE PROPFIND?
 				// Search and git commands.
 				if r.Header.Get("Search") == "true" {

From 06c1a412a6418a8cc352559ed550325ed24b0550 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 21:06:31 +0100
Subject: [PATCH 07/28] update

---
 assets/embed/templates/single.tmpl |   2 +-
 config/config.go                   |  42 ++--
 config/user.go                     |  22 ++-
 directory/file.go                  | 299 -----------------------------
 directory/editor.go => editor.go   |  36 ++--
 filemanager.go                     |  26 ++-
 info.go                            | 165 ++++++++++++++++
 directory/listing.go => listing.go | 143 +++++++++++++-
 page/page.go => page.go            |  24 +--
 directory/update.go => preput.go   |  24 +--
 10 files changed, 385 insertions(+), 398 deletions(-)
 delete mode 100644 directory/file.go
 rename directory/editor.go => editor.go (72%)
 create mode 100644 info.go
 rename directory/listing.go => listing.go (52%)
 rename page/page.go => page.go (85%)
 rename directory/update.go => preput.go (77%)

diff --git a/assets/embed/templates/single.tmpl b/assets/embed/templates/single.tmpl
index bd8f077d..d70d1646 100644
--- a/assets/embed/templates/single.tmpl
+++ b/assets/embed/templates/single.tmpl
@@ -8,7 +8,7 @@
    {{ else if eq .Type "video" }}
 
    {{ else}}
-   <pre>{{ .Content }}</pre>
+   <pre>{{ .StringifyContent }}</pre>
   {{ end }}
  </main>
 {{ end }}
diff --git a/config/config.go b/config/config.go
index 952cb134..cf008598 100644
--- a/config/config.go
+++ b/config/config.go
@@ -3,7 +3,6 @@ package config
 import (
 	"fmt"
 	"io/ioutil"
-	"net/http"
 	"regexp"
 	"strconv"
 	"strings"
@@ -17,16 +16,14 @@ import (
 // Config is a configuration for browsing in a particualr path.
 type Config struct {
 	*User
-	BaseURL       string
-	AbsoluteURL   string
-	AddrPath      string
-	Token         string // Anti CSRF token
-	HugoEnabled   bool   // Enables the Hugo plugin for File Manager
-	Users         map[string]*User
-	WebDav        bool
-	WebDavURL     string
-	WebDavHandler *webdav.Handler
-	CurrentUser   *User
+	BaseURL     string
+	AbsoluteURL string
+	AddrPath    string
+	Token       string // Anti CSRF token
+	HugoEnabled bool   // Enables the Hugo plugin for File Manager
+	Users       map[string]*User
+	WebDavURL   string
+	CurrentUser *User
 }
 
 // Rule is a dissalow/allow rule
@@ -48,8 +45,8 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 
 	appendConfig := func(cfg Config) error {
 		for _, c := range configs {
-			if c.PathScope == cfg.PathScope {
-				return fmt.Errorf("duplicate file managing config for %s", c.PathScope)
+			if c.Scope == cfg.Scope {
+				return fmt.Errorf("duplicate file managing config for %s", c.Scope)
 			}
 		}
 		configs = append(configs, cfg)
@@ -59,8 +56,8 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 	for c.Next() {
 		// Initialize the configuration with the default settings
 		cfg := Config{User: &User{}}
-		cfg.PathScope = "."
-		cfg.Root = http.Dir(cfg.PathScope)
+		cfg.Scope = "."
+		cfg.FileSystem = webdav.Dir(cfg.Scope)
 		cfg.BaseURL = ""
 		cfg.FrontMatter = "yaml"
 		cfg.HugoEnabled = false
@@ -69,7 +66,6 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 		cfg.AllowEdit = true
 		cfg.AllowNew = true
 		cfg.Commands = []string{"git", "svn", "hg"}
-		cfg.WebDav = true
 		cfg.Rules = []*Rule{&Rule{
 			Regex:  true,
 			Allow:  false,
@@ -121,9 +117,9 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 					return configs, c.ArgErr()
 				}
 
-				user.PathScope = c.Val()
-				user.PathScope = strings.TrimSuffix(user.PathScope, "/")
-				user.Root = http.Dir(user.PathScope)
+				user.Scope = c.Val()
+				user.Scope = strings.TrimSuffix(user.Scope, "/")
+				user.FileSystem = webdav.Dir(user.Scope)
 			case "styles":
 				if !c.NextArg() {
 					return configs, c.ArgErr()
@@ -227,16 +223,16 @@ func Parse(c *caddy.Controller) ([]Config, error) {
 				user.AllowNew = cfg.AllowEdit
 				user.Commands = cfg.Commands
 				user.FrontMatter = cfg.FrontMatter
-				user.PathScope = cfg.PathScope
-				user.Root = cfg.Root
+				user.Scope = cfg.Scope
+				user.FileSystem = cfg.FileSystem
 				user.Rules = cfg.Rules
 				user.StyleSheet = cfg.StyleSheet
 			}
 		}
 
-		cfg.WebDavHandler = &webdav.Handler{
+		cfg.Handler = &webdav.Handler{
 			Prefix:     cfg.WebDavURL,
-			FileSystem: webdav.Dir(cfg.PathScope),
+			FileSystem: cfg.FileSystem,
 			LockSystem: webdav.NewMemLS(),
 		}
 
diff --git a/config/user.go b/config/user.go
index abc07789..4f72d2ce 100644
--- a/config/user.go
+++ b/config/user.go
@@ -1,21 +1,23 @@
 package config
 
 import (
-	"net/http"
 	"strings"
+
+	"golang.org/x/net/webdav"
 )
 
 // User contains the configuration for each user
 type User struct {
-	PathScope     string          `json:"-"` // Path the user have access
-	Root          http.FileSystem `json:"-"` // The virtual file system the user have access
-	StyleSheet    string          `json:"-"` // Costum stylesheet
-	FrontMatter   string          `json:"-"` // Default frontmatter to save files in
-	AllowNew      bool            // Can create files and folders
-	AllowEdit     bool            // Can edit/rename files
-	AllowCommands bool            // Can execute commands
-	Commands      []string        // Available Commands
-	Rules         []*Rule         `json:"-"` // Access rules
+	Scope         string            `json:"-"` // Path the user have access
+	FileSystem    webdav.FileSystem `json:"-"` // The virtual file system the user have access
+	Handler       *webdav.Handler   `json:"-"` // The WebDav HTTP Handler
+	StyleSheet    string            `json:"-"` // Costum stylesheet
+	FrontMatter   string            `json:"-"` // Default frontmatter to save files in
+	AllowNew      bool              // Can create files and folders
+	AllowEdit     bool              // Can edit/rename files
+	AllowCommands bool              // Can execute commands
+	Commands      []string          // Available Commands
+	Rules         []*Rule           `json:"-"` // Access rules
 }
 
 // Allowed checks if the user has permission to access a directory/file
diff --git a/directory/file.go b/directory/file.go
deleted file mode 100644
index 300691da..00000000
--- a/directory/file.go
+++ /dev/null
@@ -1,299 +0,0 @@
-package directory
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"os"
-	"path"
-	"path/filepath"
-	"strings"
-	"time"
-
-	"github.com/dustin/go-humanize"
-	"github.com/hacdias/caddy-filemanager/config"
-	p "github.com/hacdias/caddy-filemanager/page"
-	"github.com/hacdias/caddy-filemanager/utils/errors"
-	"github.com/mholt/caddy/caddyhttp/httpserver"
-)
-
-// Info is the information about a particular file or directory
-type Info struct {
-	IsDir       bool
-	Name        string
-	Size        int64
-	URL         string
-	Path        string // The relative Path of the file/directory relative to Caddyfile.
-	RootPath    string // The Path of the file/directory on http.FileSystem.
-	ModTime     time.Time
-	Mode        os.FileMode
-	Mimetype    string
-	Content     string
-	Raw         []byte
-	Type        string
-	UserAllowed bool // Indicates if the user has permissions to open this directory
-}
-
-// GetInfo gets the file information and, in case of error, returns the
-// respective HTTP error code
-func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error) {
-	var err error
-
-	rootPath := strings.Replace(url.Path, c.BaseURL, "", 1)
-	rootPath = strings.TrimPrefix(rootPath, "/")
-	rootPath = "/" + rootPath
-
-	relpath := u.PathScope + rootPath
-	relpath = strings.Replace(relpath, "\\", "/", -1)
-	relpath = filepath.Clean(relpath)
-
-	file := &Info{
-		URL:      url.Path,
-		RootPath: rootPath,
-		Path:     relpath,
-	}
-	f, err := u.Root.Open(rootPath)
-	if err != nil {
-		return file, errors.ToHTTPCode(err), err
-	}
-	defer f.Close()
-
-	info, err := f.Stat()
-	if err != nil {
-		return file, errors.ToHTTPCode(err), err
-	}
-
-	file.IsDir = info.IsDir()
-	file.ModTime = info.ModTime()
-	file.Name = info.Name()
-	file.Size = info.Size()
-	return file, 0, nil
-}
-
-// GetExtendedInfo is used to get extra parameters for FileInfo struct
-func (i *Info) GetExtendedInfo() error {
-	err := i.Read()
-	if err != nil {
-		return err
-	}
-
-	i.Type = SimplifyMimeType(i.Mimetype)
-	return nil
-}
-
-// Read is used to read a file and store its content
-func (i *Info) Read() error {
-	raw, err := ioutil.ReadFile(i.Path)
-	if err != nil {
-		return err
-	}
-	i.Mimetype = http.DetectContentType(raw)
-	i.Content = string(raw)
-	i.Raw = raw
-	return nil
-}
-
-// HumanSize returns the size of the file as a human-readable string
-// in IEC format (i.e. power of 2 or base 1024).
-func (i Info) HumanSize() string {
-	return humanize.IBytes(uint64(i.Size))
-}
-
-// HumanModTime returns the modified time of the file as a human-readable string.
-func (i Info) HumanModTime(format string) string {
-	return i.ModTime.Format(format)
-}
-
-// ServeAsHTML is used to serve single file pages
-func (i *Info) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	if i.IsDir {
-		return i.serveListing(w, r, c, u)
-	}
-
-	return i.serveSingleFile(w, r, c, u)
-}
-
-func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	err := i.GetExtendedInfo()
-	if err != nil {
-		return errors.ToHTTPCode(err), err
-	}
-
-	if i.Type == "blob" {
-		http.Redirect(w, r, c.AddrPath+r.URL.Path+"?download=true", http.StatusTemporaryRedirect)
-		return 0, nil
-	}
-
-	page := &p.Page{
-		Info: &p.Info{
-			Name:   i.Name,
-			Path:   i.RootPath,
-			IsDir:  false,
-			Data:   i,
-			User:   u,
-			Config: c,
-		},
-	}
-
-	if CanBeEdited(i.Name) && u.AllowEdit {
-		editor, err := i.GetEditor()
-
-		if err != nil {
-			return http.StatusInternalServerError, err
-		}
-
-		page.Info.Data = editor
-		return page.PrintAsHTML(w, "frontmatter", "editor")
-	}
-
-	return page.PrintAsHTML(w, "single")
-}
-
-func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	var err error
-
-	file, err := u.Root.Open(i.RootPath)
-	if err != nil {
-		return errors.ToHTTPCode(err), err
-	}
-	defer file.Close()
-
-	listing, err := i.loadDirectoryContents(file, r.URL.Path, u)
-	if err != nil {
-		fmt.Println(err)
-		switch {
-		case os.IsPermission(err):
-			return http.StatusForbidden, err
-		case os.IsExist(err):
-			return http.StatusGone, err
-		default:
-			return http.StatusInternalServerError, err
-		}
-	}
-
-	listing.Context = httpserver.Context{
-		Root: c.Root,
-		Req:  r,
-		URL:  r.URL,
-	}
-
-	// Copy the query values into the Listing struct
-	var limit int
-	listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.PathScope)
-	if err != nil {
-		return http.StatusBadRequest, err
-	}
-
-	listing.applySort()
-
-	if limit > 0 && limit <= len(listing.Items) {
-		listing.Items = listing.Items[:limit]
-		listing.ItemsLimitedTo = limit
-	}
-
-	if strings.Contains(r.Header.Get("Accept"), "application/json") {
-		marsh, err := json.Marshal(listing.Items)
-		if err != nil {
-			return http.StatusInternalServerError, err
-		}
-
-		w.Header().Set("Content-Type", "application/json; charset=utf-8")
-		if _, err := w.Write(marsh); err != nil {
-			return http.StatusInternalServerError, err
-		}
-
-		return http.StatusOK, nil
-	}
-
-	page := &p.Page{
-		Info: &p.Info{
-			Name:   listing.Name,
-			Path:   i.RootPath,
-			IsDir:  true,
-			User:   u,
-			Config: c,
-			Data:   listing,
-		},
-	}
-
-	if r.Header.Get("Minimal") == "true" {
-		page.Minimal = true
-	}
-
-	return page.PrintAsHTML(w, "listing")
-}
-
-func (i Info) loadDirectoryContents(file http.File, path string, u *config.User) (*Listing, error) {
-	files, err := file.Readdir(-1)
-	if err != nil {
-		return nil, err
-	}
-
-	listing := directoryListing(files, i.RootPath, path, u)
-	return &listing, nil
-}
-
-func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *config.User) Listing {
-	var (
-		fileinfos           []Info
-		dirCount, fileCount int
-	)
-
-	for _, f := range files {
-		name := f.Name()
-
-		if f.IsDir() {
-			name += "/"
-			dirCount++
-		} else {
-			fileCount++
-		}
-
-		// Absolute URL
-		url := url.URL{Path: basePath + name}
-		fileinfos = append(fileinfos, Info{
-			IsDir:       f.IsDir(),
-			Name:        f.Name(),
-			Size:        f.Size(),
-			URL:         url.String(),
-			ModTime:     f.ModTime().UTC(),
-			Mode:        f.Mode(),
-			UserAllowed: u.Allowed(url.String()),
-		})
-	}
-
-	return Listing{
-		Name:     path.Base(urlPath),
-		Path:     urlPath,
-		Items:    fileinfos,
-		NumDirs:  dirCount,
-		NumFiles: fileCount,
-	}
-}
-
-// SimplifyMimeType returns the base type of a file
-func SimplifyMimeType(name string) string {
-	if strings.HasPrefix(name, "video") {
-		return "video"
-	}
-
-	if strings.HasPrefix(name, "audio") {
-		return "audio"
-	}
-
-	if strings.HasPrefix(name, "image") {
-		return "image"
-	}
-
-	if strings.HasPrefix(name, "text") {
-		return "text"
-	}
-
-	if strings.HasPrefix(name, "application/javascript") {
-		return "text"
-	}
-
-	return "blob"
-}
diff --git a/directory/editor.go b/editor.go
similarity index 72%
rename from directory/editor.go
rename to editor.go
index b6b43371..d26efe0d 100644
--- a/directory/editor.go
+++ b/editor.go
@@ -1,4 +1,4 @@
-package directory
+package filemanager
 
 import (
 	"bytes"
@@ -18,10 +18,10 @@ type Editor struct {
 }
 
 // GetEditor gets the editor based on a FileInfo struct
-func (i *Info) GetEditor() (*Editor, error) {
+func (i *FileInfo) GetEditor() (*Editor, error) {
 	// Create a new editor variable and set the mode
 	editor := new(Editor)
-	editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name), ".")
+	editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name()), ".")
 
 	switch editor.Mode {
 	case "md", "markdown", "mdown", "mmark":
@@ -42,20 +42,20 @@ func (i *Info) GetEditor() (*Editor, error) {
 	// Handle the content depending on the file extension
 	switch editor.Mode {
 	case "markdown", "asciidoc", "rst":
-		if !HasFrontMatterRune(i.Raw) {
+		if !hasFrontMatterRune(i.Content) {
 			editor.Class = "content-only"
-			editor.Content = i.Content
+			editor.Content = i.StringifyContent()
 			break
 		}
 
 		// Starts a new buffer and parses the file using Hugo's functions
-		buffer := bytes.NewBuffer(i.Raw)
+		buffer := bytes.NewBuffer(i.Content)
 		page, err = parser.ReadFrom(buffer)
 		editor.Class = "complete"
 
 		if err != nil {
 			editor.Class = "content-only"
-			editor.Content = i.Content
+			editor.Content = i.StringifyContent()
 			break
 		}
 
@@ -67,35 +67,35 @@ func (i *Info) GetEditor() (*Editor, error) {
 		editor.Class = "frontmatter-only"
 
 		// Checks if the file already has the frontmatter rune and parses it
-		if HasFrontMatterRune(i.Raw) {
-			editor.FrontMatter, _, err = frontmatter.Pretty(i.Raw)
+		if hasFrontMatterRune(i.Content) {
+			editor.FrontMatter, _, err = frontmatter.Pretty(i.Content)
 		} else {
-			editor.FrontMatter, _, err = frontmatter.Pretty(AppendFrontMatterRune(i.Raw, editor.Mode))
+			editor.FrontMatter, _, err = frontmatter.Pretty(appendFrontMatterRune(i.Content, editor.Mode))
 		}
 
 		// Check if there were any errors
 		if err != nil {
 			editor.Class = "content-only"
-			editor.Content = i.Content
+			editor.Content = i.StringifyContent()
 			break
 		}
 	default:
 		editor.Class = "content-only"
-		editor.Content = i.Content
+		editor.Content = i.StringifyContent()
 	}
 
 	return editor, nil
 }
 
-// HasFrontMatterRune checks if the file has the frontmatter rune
-func HasFrontMatterRune(file []byte) bool {
+// hasFrontMatterRune checks if the file has the frontmatter rune
+func hasFrontMatterRune(file []byte) bool {
 	return strings.HasPrefix(string(file), "---") ||
 		strings.HasPrefix(string(file), "+++") ||
 		strings.HasPrefix(string(file), "{")
 }
 
-// AppendFrontMatterRune appends the frontmatter rune to a file
-func AppendFrontMatterRune(frontmatter []byte, language string) []byte {
+// appendFrontMatterRune appends the frontmatter rune to a file
+func appendFrontMatterRune(frontmatter []byte, language string) []byte {
 	switch language {
 	case "yaml":
 		return []byte("---\n" + string(frontmatter) + "\n---")
@@ -108,8 +108,8 @@ func AppendFrontMatterRune(frontmatter []byte, language string) []byte {
 	return frontmatter
 }
 
-// CanBeEdited checks if the extension of a file is supported by the editor
-func CanBeEdited(filename string) bool {
+// canBeEdited checks if the extension of a file is supported by the editor
+func canBeEdited(filename string) bool {
 	extensions := [...]string{
 		"md", "markdown", "mdown", "mmark",
 		"asciidoc", "adoc", "ad",
diff --git a/filemanager.go b/filemanager.go
index 3efc3cd6..bc8f1e18 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -16,9 +16,7 @@ import (
 
 	"github.com/hacdias/caddy-filemanager/assets"
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/directory"
 	"github.com/hacdias/caddy-filemanager/errors"
-	"github.com/hacdias/caddy-filemanager/page"
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
 
@@ -33,7 +31,7 @@ type FileManager struct {
 func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 	var (
 		c    *config.Config
-		fi   *directory.Info
+		fi   *FileInfo
 		code int
 		err  error
 		user *config.User
@@ -78,7 +76,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 					}
 				}
 
-				c.WebDavHandler.ServeHTTP(w, r)
+				c.Handler.ServeHTTP(w, r)
 				return 0, nil
 			}
 
@@ -96,7 +94,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 			if r.Method == http.MethodGet {
 				// Gets the information of the directory/file
-				fi, code, err = directory.GetInfo(r.URL, c, user)
+				fi, code, err = GetInfo(r.URL, c, user)
 				if err != nil {
 					if r.Method == http.MethodGet {
 						return errors.PrintHTML(w, code, err)
@@ -106,7 +104,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 				// If it's a dir and the path doesn't end with a trailing slash,
 				// redirect the user.
-				if fi.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
+				if fi.IsDir() && !strings.HasSuffix(r.URL.Path, "/") {
 					http.Redirect(w, r, c.AddrPath+r.URL.Path+"/", http.StatusTemporaryRedirect)
 					return 0, nil
 				}
@@ -114,23 +112,23 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				// Generate anti security token.
 				c.GenerateToken()
 
-				if !fi.IsDir {
+				if !fi.IsDir() {
 					query := r.URL.Query()
 					if val, ok := query["raw"]; ok && val[0] == "true" {
 						r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
-						c.WebDavHandler.ServeHTTP(w, r)
+						c.Handler.ServeHTTP(w, r)
 						return 0, nil
 					}
 
 					if val, ok := query["download"]; ok && val[0] == "true" {
-						w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name)
+						w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name())
 						r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
-						c.WebDavHandler.ServeHTTP(w, r)
+						c.Handler.ServeHTTP(w, r)
 						return 0, nil
 					}
 				}
 
-				code, err := fi.ServeAsHTML(w, r, c, user)
+				code, err := fi.ServeHTTP(w, r, c, user)
 				if err != nil {
 					return errors.PrintHTML(w, code, err)
 				}
@@ -189,7 +187,7 @@ func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config
 		return http.StatusNotImplemented, nil
 	}
 
-	path := strings.Replace(r.URL.Path, c.BaseURL, c.PathScope, 1)
+	path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1)
 	path = filepath.Clean(path)
 
 	cmd := exec.Command(command[0], command[1:len(command)]...)
@@ -200,6 +198,6 @@ func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config
 		return http.StatusInternalServerError, err
 	}
 
-	page := &page.Page{Info: &page.Info{Data: string(output)}}
-	return page.PrintAsJSON(w)
+	p := &page{pageInfo: &pageInfo{Data: string(output)}}
+	return p.PrintAsJSON(w)
 }
diff --git a/info.go b/info.go
new file mode 100644
index 00000000..ed6da747
--- /dev/null
+++ b/info.go
@@ -0,0 +1,165 @@
+package filemanager
+
+import (
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"path/filepath"
+	"strings"
+
+	humanize "github.com/dustin/go-humanize"
+	"github.com/hacdias/caddy-filemanager/config"
+)
+
+// FileInfo contains the information about a particular file or directory
+type FileInfo struct {
+	os.FileInfo
+	URL         string
+	Path        string // Relative path to Caddyfile
+	VirtualPath string // Relative path to u.FileSystem
+	Mimetype    string
+	Content     []byte
+	Type        string
+	UserAllowed bool // Indicates if the user has enough permissions
+}
+
+// GetInfo gets the file information and, in case of error, returns the
+// respective HTTP error code
+func GetInfo(url *url.URL, c *config.Config, u *config.User) (*FileInfo, int, error) {
+	var err error
+
+	i := &FileInfo{URL: url.Path}
+	i.VirtualPath = strings.Replace(url.Path, c.BaseURL, "", 1)
+	i.VirtualPath = strings.TrimPrefix(i.VirtualPath, "/")
+	i.VirtualPath = "/" + i.VirtualPath
+
+	i.Path = u.Scope + i.VirtualPath
+	i.Path = strings.Replace(i.Path, "\\", "/", -1)
+	i.Path = filepath.Clean(i.Path)
+
+	i.FileInfo, err = os.Stat(i.Path)
+	if err != nil {
+		code := http.StatusInternalServerError
+
+		switch {
+		case os.IsPermission(err):
+			code = http.StatusForbidden
+		case os.IsNotExist(err):
+			code = http.StatusGone
+		case os.IsExist(err):
+			code = http.StatusGone
+		}
+
+		return i, code, err
+	}
+
+	return i, 0, nil
+}
+
+func (i *FileInfo) Read() error {
+	var err error
+	i.Content, err = ioutil.ReadFile(i.Path)
+	if err != nil {
+		return err
+	}
+	i.Mimetype = http.DetectContentType(i.Content)
+	i.Type = SimplifyMimeType(i.Mimetype)
+	return nil
+}
+
+func (i FileInfo) StringifyContent() string {
+	return string(i.Content)
+}
+
+// HumanSize returns the size of the file as a human-readable string
+// in IEC format (i.e. power of 2 or base 1024).
+func (i FileInfo) HumanSize() string {
+	return humanize.IBytes(uint64(i.Size()))
+}
+
+// HumanModTime returns the modified time of the file as a human-readable string.
+func (i FileInfo) HumanModTime(format string) string {
+	return i.ModTime().Format(format)
+}
+
+func (i *FileInfo) ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+	if i.IsDir() {
+		return i.serveListing(w, r, c, u)
+	}
+
+	return i.serveSingleFile(w, r, c, u)
+}
+
+func (i *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+	err := i.Read()
+	if err != nil {
+		code := http.StatusInternalServerError
+
+		switch {
+		case os.IsPermission(err):
+			code = http.StatusForbidden
+		case os.IsNotExist(err):
+			code = http.StatusGone
+		case os.IsExist(err):
+			code = http.StatusGone
+		}
+
+		return code, err
+	}
+
+	if i.Type == "blob" {
+		http.Redirect(
+			w, r,
+			c.AddrPath+r.URL.Path+"?download=true",
+			http.StatusTemporaryRedirect,
+		)
+		return 0, nil
+	}
+
+	p := &page{
+		pageInfo: &pageInfo{
+			Name:   i.Name(),
+			Path:   i.VirtualPath,
+			IsDir:  false,
+			Data:   i,
+			User:   u,
+			Config: c,
+		},
+	}
+
+	if (canBeEdited(i.Name()) || i.Type == "text") && u.AllowEdit {
+		p.Data, err = i.GetEditor()
+		if err != nil {
+			return http.StatusInternalServerError, err
+		}
+
+		return p.PrintAsHTML(w, "frontmatter", "editor")
+	}
+
+	return p.PrintAsHTML(w, "single")
+}
+
+func SimplifyMimeType(name string) string {
+	if strings.HasPrefix(name, "video") {
+		return "video"
+	}
+
+	if strings.HasPrefix(name, "audio") {
+		return "audio"
+	}
+
+	if strings.HasPrefix(name, "image") {
+		return "image"
+	}
+
+	if strings.HasPrefix(name, "text") {
+		return "text"
+	}
+
+	if strings.HasPrefix(name, "application/javascript") {
+		return "text"
+	}
+
+	return "blob"
+}
diff --git a/directory/listing.go b/listing.go
similarity index 52%
rename from directory/listing.go
rename to listing.go
index 0aa87fa8..78dc6b47 100644
--- a/directory/listing.go
+++ b/listing.go
@@ -1,11 +1,18 @@
-package directory
+package filemanager
 
 import (
+	"encoding/json"
+	"fmt"
 	"net/http"
+	"net/url"
+	"os"
+	"path"
 	"sort"
 	"strconv"
 	"strings"
 
+	"github.com/hacdias/caddy-filemanager/config"
+	"github.com/hacdias/caddy-filemanager/utils/errors"
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
 
@@ -16,7 +23,7 @@ type Listing struct {
 	// The full path of the request
 	Path string
 	// The items (files and folders) in the path
-	Items []Info
+	Items []FileInfo
 	// The number of directories in the listing
 	NumDirs int
 	// The number of files (items that aren't directories) in the listing
@@ -77,15 +84,15 @@ func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
 
 // Treat upper and lower case equally
 func (l byName) Less(i, j int) bool {
-	if l.Items[i].IsDir && !l.Items[j].IsDir {
+	if l.Items[i].IsDir() && !l.Items[j].IsDir() {
 		return true
 	}
 
-	if !l.Items[i].IsDir && l.Items[j].IsDir {
+	if !l.Items[i].IsDir() && l.Items[j].IsDir() {
 		return false
 	}
 
-	return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name)
+	return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name())
 }
 
 // By Size
@@ -94,11 +101,11 @@ func (l bySize) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
 
 const directoryOffset = -1 << 31 // = math.MinInt32
 func (l bySize) Less(i, j int) bool {
-	iSize, jSize := l.Items[i].Size, l.Items[j].Size
-	if l.Items[i].IsDir {
+	iSize, jSize := l.Items[i].Size(), l.Items[j].Size()
+	if l.Items[i].IsDir() {
 		iSize = directoryOffset + iSize
 	}
-	if l.Items[j].IsDir {
+	if l.Items[j].IsDir() {
 		jSize = directoryOffset + jSize
 	}
 	return iSize < jSize
@@ -107,7 +114,7 @@ func (l bySize) Less(i, j int) bool {
 // By Time
 func (l byTime) Len() int           { return len(l.Items) }
 func (l byTime) Swap(i, j int)      { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
-func (l byTime) Less(i, j int) bool { return l.Items[i].ModTime.Before(l.Items[j].ModTime) }
+func (l byTime) Less(i, j int) bool { return l.Items[i].ModTime().Before(l.Items[j].ModTime()) }
 
 // Add sorting method to "Listing"
 // it will apply what's in ".Sort" and ".Order"
@@ -139,3 +146,121 @@ func (l Listing) applySort() {
 		}
 	}
 }
+
+func (i *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+	var err error
+
+	file, err := u.FileSystem.OpenFile(i.VirtualPath, os.O_RDONLY, 0)
+	if err != nil {
+		return errors.ToHTTPCode(err), err
+	}
+	defer file.Close()
+
+	listing, err := i.loadDirectoryContents(file, r.URL.Path, u)
+	if err != nil {
+		fmt.Println(err)
+		switch {
+		case os.IsPermission(err):
+			return http.StatusForbidden, err
+		case os.IsExist(err):
+			return http.StatusGone, err
+		default:
+			return http.StatusInternalServerError, err
+		}
+	}
+
+	listing.Context = httpserver.Context{
+		Root: http.Dir(u.Scope),
+		Req:  r,
+		URL:  r.URL,
+	}
+
+	// Copy the query values into the Listing struct
+	var limit int
+	listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.Scope)
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	listing.applySort()
+
+	if limit > 0 && limit <= len(listing.Items) {
+		listing.Items = listing.Items[:limit]
+		listing.ItemsLimitedTo = limit
+	}
+
+	if strings.Contains(r.Header.Get("Accept"), "application/json") {
+		marsh, err := json.Marshal(listing.Items)
+		if err != nil {
+			return http.StatusInternalServerError, err
+		}
+
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		if _, err := w.Write(marsh); err != nil {
+			return http.StatusInternalServerError, err
+		}
+
+		return http.StatusOK, nil
+	}
+
+	page := &page{
+		pageInfo: &pageInfo{
+			Name:   listing.Name,
+			Path:   i.VirtualPath,
+			IsDir:  true,
+			User:   u,
+			Config: c,
+			Data:   listing,
+		},
+	}
+
+	if r.Header.Get("Minimal") == "true" {
+		page.Minimal = true
+	}
+
+	return page.PrintAsHTML(w, "listing")
+}
+
+func (i FileInfo) loadDirectoryContents(file http.File, path string, u *config.User) (*Listing, error) {
+	files, err := file.Readdir(-1)
+	if err != nil {
+		return nil, err
+	}
+
+	listing := directoryListing(files, i.VirtualPath, path, u)
+	return &listing, nil
+}
+
+func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *config.User) Listing {
+	var (
+		fileinfos           []FileInfo
+		dirCount, fileCount int
+	)
+
+	for _, f := range files {
+		name := f.Name()
+
+		if f.IsDir() {
+			name += "/"
+			dirCount++
+		} else {
+			fileCount++
+		}
+
+		// Absolute URL
+		url := url.URL{Path: basePath + name}
+		fileinfos = append(fileinfos, FileInfo{
+			FileInfo:    f,
+			URL:         url.String(),
+			UserAllowed: u.Allowed(url.String()),
+		})
+	}
+
+	return Listing{
+		Name:     path.Base(urlPath),
+		Path:     urlPath,
+		Items:    fileinfos,
+		NumDirs:  dirCount,
+		NumFiles: fileCount,
+	}
+}
diff --git a/page/page.go b/page.go
similarity index 85%
rename from page/page.go
rename to page.go
index a8266dc6..f511b7a8 100644
--- a/page/page.go
+++ b/page.go
@@ -1,4 +1,4 @@
-package page
+package filemanager
 
 import (
 	"bytes"
@@ -13,14 +13,14 @@ import (
 	"github.com/hacdias/caddy-filemanager/utils/variables"
 )
 
-// Page contains the informations and functions needed to show the page
-type Page struct {
-	*Info
+// page contains the informations and functions needed to show the page
+type page struct {
+	*pageInfo
 	Minimal bool
 }
 
-// Info contains the information of a page
-type Info struct {
+// pageInfo contains the information of a page
+type pageInfo struct {
 	Name   string
 	Path   string
 	IsDir  bool
@@ -31,7 +31,7 @@ type Info struct {
 
 // BreadcrumbMap returns p.Path where every element is a map
 // of URLs and path segment names.
-func (i Info) BreadcrumbMap() map[string]string {
+func (i pageInfo) BreadcrumbMap() map[string]string {
 	result := map[string]string{}
 
 	if len(i.Path) == 0 {
@@ -62,7 +62,7 @@ func (i Info) BreadcrumbMap() map[string]string {
 }
 
 // PreviousLink returns the path of the previous folder
-func (i Info) PreviousLink() string {
+func (i pageInfo) PreviousLink() string {
 	path := strings.TrimSuffix(i.Path, "/")
 	path = strings.TrimPrefix(path, "/")
 	path = i.Config.AbsoluteURL + "/" + path
@@ -76,7 +76,7 @@ func (i Info) PreviousLink() string {
 }
 
 // PrintAsHTML formats the page in HTML and executes the template
-func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) {
+func (p page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) {
 	// Create the functions map, then the template, check for erros and
 	// execute the template if there aren't errors
 	functions := template.FuncMap{
@@ -124,7 +124,7 @@ func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
 	}
 
 	buf := &bytes.Buffer{}
-	err := tpl.Execute(buf, p.Info)
+	err := tpl.Execute(buf, p.pageInfo)
 
 	if err != nil {
 		return http.StatusInternalServerError, err
@@ -136,8 +136,8 @@ func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
 }
 
 // PrintAsJSON prints the current page infromation in JSON
-func (p Page) PrintAsJSON(w http.ResponseWriter) (int, error) {
-	marsh, err := json.Marshal(p.Info.Data)
+func (p page) PrintAsJSON(w http.ResponseWriter) (int, error) {
+	marsh, err := json.Marshal(p.pageInfo.Data)
 	if err != nil {
 		return http.StatusInternalServerError, err
 	}
diff --git a/directory/update.go b/preput.go
similarity index 77%
rename from directory/update.go
rename to preput.go
index 10483769..f2285910 100644
--- a/directory/update.go
+++ b/preput.go
@@ -1,4 +1,4 @@
-package directory
+package filemanager
 
 import (
 	"bytes"
@@ -15,7 +15,7 @@ import (
 )
 
 // Update is used to update a file that was edited
-func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+func (i *FileInfo) Update(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	var (
 		data      map[string]interface{}
 		file      []byte
@@ -38,7 +38,7 @@ func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config,
 
 	switch kind {
 	case "frontmatter-only":
-		if file, code, err = ParseFrontMatterOnlyFile(data, i.Name); err != nil {
+		if file, code, err = parseFrontMatterOnlyFile(data, i.Name()); err != nil {
 			return http.StatusInternalServerError, err
 		}
 	case "content-only":
@@ -46,7 +46,7 @@ func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config,
 		mainContent = strings.TrimSpace(mainContent)
 		file = []byte(mainContent)
 	case "complete":
-		if file, code, err = ParseCompleteFile(data, i.Name, u.FrontMatter); err != nil {
+		if file, code, err = parseCompleteFile(data, i.Name(), u.FrontMatter); err != nil {
 			return http.StatusInternalServerError, err
 		}
 	default:
@@ -58,10 +58,10 @@ func (i *Info) Update(w http.ResponseWriter, r *http.Request, c *config.Config,
 	return code, nil
 }
 
-// ParseFrontMatterOnlyFile parses a frontmatter only file
-func ParseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, int, error) {
+// parseFrontMatterOnlyFile parses a frontmatter only file
+func parseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, int, error) {
 	frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".")
-	f, code, err := ParseFrontMatter(data, frontmatter)
+	f, code, err := parseFrontMatter(data, frontmatter)
 	fString := string(f)
 
 	// If it's toml or yaml, strip frontmatter identifier
@@ -79,8 +79,8 @@ func ParseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, int, e
 	return f, code, err
 }
 
-// ParseFrontMatter is the frontmatter parser
-func ParseFrontMatter(data interface{}, frontmatter string) ([]byte, int, error) {
+// parseFrontMatter is the frontmatter parser
+func parseFrontMatter(data interface{}, frontmatter string) ([]byte, int, error) {
 	var mark rune
 
 	switch frontmatter {
@@ -103,8 +103,8 @@ func ParseFrontMatter(data interface{}, frontmatter string) ([]byte, int, error)
 	return f, http.StatusOK, nil
 }
 
-// ParseCompleteFile parses a complete file
-func ParseCompleteFile(data map[string]interface{}, filename string, frontmatter string) ([]byte, int, error) {
+// parseCompleteFile parses a complete file
+func parseCompleteFile(data map[string]interface{}, filename string, frontmatter string) ([]byte, int, error) {
 	mainContent := ""
 
 	if _, ok := data["content"]; ok {
@@ -120,7 +120,7 @@ func ParseCompleteFile(data map[string]interface{}, filename string, frontmatter
 		data["date"] = data["date"].(string) + ":00"
 	}
 
-	front, code, err := ParseFrontMatter(data, frontmatter)
+	front, code, err := parseFrontMatter(data, frontmatter)
 
 	if err != nil {
 		fmt.Println(frontmatter)

From f2fbe92591df25ac96fcf4a8ab80f4978f0d4a56 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 21:30:10 +0100
Subject: [PATCH 08/28] organize better in sub packages

---
 errors/errors.go                        |  3 ++
 filemanager.go                          |  3 +-
 frontmatter/frontmatter.go              |  7 +++--
 info.go                                 |  5 ++--
 listing.go                              | 10 ++++---
 page.go => page/page.go                 | 37 +++++++++++++------------
 utils/{errors/http.go => errors.go}     | 11 +++++---
 utils/{variables => }/types.go          |  2 +-
 utils/{variables => }/variables.go      |  2 +-
 utils/{variables => }/variables_test.go |  2 +-
 10 files changed, 47 insertions(+), 35 deletions(-)
 rename page.go => page/page.go (76%)
 rename utils/{errors/http.go => errors.go} (62%)
 rename utils/{variables => }/types.go (94%)
 rename utils/{variables => }/variables.go (98%)
 rename utils/{variables => }/variables_test.go (97%)

diff --git a/errors/errors.go b/errors/errors.go
index d404e54e..b859a345 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -32,6 +32,9 @@ const template = `<!DOCTYPE html>
         color: #eee;
         font-weight: bold;
     }
+	p {
+		line-height: 1.3;
+	}
     </style>
 </head>
 
diff --git a/filemanager.go b/filemanager.go
index bc8f1e18..8532159f 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -17,6 +17,7 @@ import (
 	"github.com/hacdias/caddy-filemanager/assets"
 	"github.com/hacdias/caddy-filemanager/config"
 	"github.com/hacdias/caddy-filemanager/errors"
+	"github.com/hacdias/caddy-filemanager/page"
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
 
@@ -198,6 +199,6 @@ func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config
 		return http.StatusInternalServerError, err
 	}
 
-	p := &page{pageInfo: &pageInfo{Data: string(output)}}
+	p := &page.Page{Info: &page.Info{Data: string(output)}}
 	return p.PrintAsJSON(w)
 }
diff --git a/frontmatter/frontmatter.go b/frontmatter/frontmatter.go
index 5703addc..18edbc5b 100644
--- a/frontmatter/frontmatter.go
+++ b/frontmatter/frontmatter.go
@@ -13,7 +13,8 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/BurntSushi/toml"
-	"github.com/hacdias/caddy-filemanager/utils/variables"
+	"github.com/hacdias/caddy-filemanager/utils"
+
 	"github.com/spf13/cast"
 )
 
@@ -125,9 +126,9 @@ func rawToPretty(config interface{}, parent *Block) *Content {
 	}
 
 	for name, element := range cnf {
-		if variables.IsMap(element) {
+		if utils.IsMap(element) {
 			objects = append(objects, handleObjects(element, parent, name))
-		} else if variables.IsSlice(element) {
+		} else if utils.IsSlice(element) {
 			arrays = append(arrays, handleArrays(element, parent, name))
 		} else {
 			if name == "title" && parent.Name == mainName {
diff --git a/info.go b/info.go
index ed6da747..542c13b3 100644
--- a/info.go
+++ b/info.go
@@ -10,6 +10,7 @@ import (
 
 	humanize "github.com/dustin/go-humanize"
 	"github.com/hacdias/caddy-filemanager/config"
+	"github.com/hacdias/caddy-filemanager/page"
 )
 
 // FileInfo contains the information about a particular file or directory
@@ -117,8 +118,8 @@ func (i *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *co
 		return 0, nil
 	}
 
-	p := &page{
-		pageInfo: &pageInfo{
+	p := &page.Page{
+		Info: &page.Info{
 			Name:   i.Name(),
 			Path:   i.VirtualPath,
 			IsDir:  false,
diff --git a/listing.go b/listing.go
index 78dc6b47..e57be46f 100644
--- a/listing.go
+++ b/listing.go
@@ -12,7 +12,9 @@ import (
 	"strings"
 
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/utils/errors"
+	"github.com/hacdias/caddy-filemanager/page"
+	"github.com/hacdias/caddy-filemanager/utils"
+
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
 
@@ -152,7 +154,7 @@ func (i *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *confi
 
 	file, err := u.FileSystem.OpenFile(i.VirtualPath, os.O_RDONLY, 0)
 	if err != nil {
-		return errors.ToHTTPCode(err), err
+		return utils.ErrorToHTTPCode(err, true), err
 	}
 	defer file.Close()
 
@@ -203,8 +205,8 @@ func (i *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *confi
 		return http.StatusOK, nil
 	}
 
-	page := &page{
-		pageInfo: &pageInfo{
+	page := &page.Page{
+		Info: &page.Info{
 			Name:   listing.Name,
 			Path:   i.VirtualPath,
 			IsDir:  true,
diff --git a/page.go b/page/page.go
similarity index 76%
rename from page.go
rename to page/page.go
index f511b7a8..2b2ca46e 100644
--- a/page.go
+++ b/page/page.go
@@ -1,4 +1,5 @@
-package filemanager
+// Package page is used to render the HTML to the end user
+package page
 
 import (
 	"bytes"
@@ -10,17 +11,17 @@ import (
 
 	"github.com/hacdias/caddy-filemanager/assets"
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/utils/variables"
+	"github.com/hacdias/caddy-filemanager/utils"
 )
 
-// page contains the informations and functions needed to show the page
-type page struct {
-	*pageInfo
+// Page contains the informations and functions needed to show the Page
+type Page struct {
+	*Info
 	Minimal bool
 }
 
-// pageInfo contains the information of a page
-type pageInfo struct {
+// Info contains the information of a Page
+type Info struct {
 	Name   string
 	Path   string
 	IsDir  bool
@@ -31,7 +32,7 @@ type pageInfo struct {
 
 // BreadcrumbMap returns p.Path where every element is a map
 // of URLs and path segment names.
-func (i pageInfo) BreadcrumbMap() map[string]string {
+func (i Info) BreadcrumbMap() map[string]string {
 	result := map[string]string{}
 
 	if len(i.Path) == 0 {
@@ -62,7 +63,7 @@ func (i pageInfo) BreadcrumbMap() map[string]string {
 }
 
 // PreviousLink returns the path of the previous folder
-func (i pageInfo) PreviousLink() string {
+func (i Info) PreviousLink() string {
 	path := strings.TrimSuffix(i.Path, "/")
 	path = strings.TrimPrefix(path, "/")
 	path = i.Config.AbsoluteURL + "/" + path
@@ -76,11 +77,11 @@ func (i pageInfo) PreviousLink() string {
 }
 
 // PrintAsHTML formats the page in HTML and executes the template
-func (p page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) {
+func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) {
 	// Create the functions map, then the template, check for erros and
 	// execute the template if there aren't errors
 	functions := template.FuncMap{
-		"Defined": variables.Defined,
+		"Defined": utils.Defined,
 		"CSS": func(s string) template.CSS {
 			return template.CSS(s)
 		},
@@ -101,7 +102,7 @@ func (p page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
 	// For each template, add it to the the tpl variable
 	for i, t := range templates {
 		// Get the template from the assets
-		page, err := assets.Asset("templates/" + t + ".tmpl")
+		Page, err := assets.Asset("templates/" + t + ".tmpl")
 
 		// Check if there is some error. If so, the template doesn't exist
 		if err != nil {
@@ -112,9 +113,9 @@ func (p page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
 		// If it's the first iteration, creates a new template and add the
 		// functions map
 		if i == 0 {
-			tpl, err = template.New(t).Funcs(functions).Parse(string(page))
+			tpl, err = template.New(t).Funcs(functions).Parse(string(Page))
 		} else {
-			tpl, err = tpl.Parse(string(page))
+			tpl, err = tpl.Parse(string(Page))
 		}
 
 		if err != nil {
@@ -124,7 +125,7 @@ func (p page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
 	}
 
 	buf := &bytes.Buffer{}
-	err := tpl.Execute(buf, p.pageInfo)
+	err := tpl.Execute(buf, p.Info)
 
 	if err != nil {
 		return http.StatusInternalServerError, err
@@ -135,9 +136,9 @@ func (p page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
 	return http.StatusOK, nil
 }
 
-// PrintAsJSON prints the current page infromation in JSON
-func (p page) PrintAsJSON(w http.ResponseWriter) (int, error) {
-	marsh, err := json.Marshal(p.pageInfo.Data)
+// PrintAsJSON prints the current Page infromation in JSON
+func (p Page) PrintAsJSON(w http.ResponseWriter) (int, error) {
+	marsh, err := json.Marshal(p.Info.Data)
 	if err != nil {
 		return http.StatusInternalServerError, err
 	}
diff --git a/utils/errors/http.go b/utils/errors.go
similarity index 62%
rename from utils/errors/http.go
rename to utils/errors.go
index 644f9c55..35841d85 100644
--- a/utils/errors/http.go
+++ b/utils/errors.go
@@ -1,17 +1,20 @@
-package errors
+package utils
 
 import (
 	"net/http"
 	"os"
 )
 
-// ToHTTPCode gets the respective HTTP code for an error
-func ToHTTPCode(err error) int {
+func ErrorToHTTPCode(err error, gone bool) int {
 	switch {
 	case os.IsPermission(err):
 		return http.StatusForbidden
 	case os.IsNotExist(err):
-		return http.StatusNotFound
+		if !gone {
+			return http.StatusNotFound
+		}
+
+		return http.StatusGone
 	case os.IsExist(err):
 		return http.StatusGone
 	default:
diff --git a/utils/variables/types.go b/utils/types.go
similarity index 94%
rename from utils/variables/types.go
rename to utils/types.go
index ee43dad3..7e6b408b 100644
--- a/utils/variables/types.go
+++ b/utils/types.go
@@ -1,4 +1,4 @@
-package variables
+package utils
 
 import "reflect"
 
diff --git a/utils/variables/variables.go b/utils/variables.go
similarity index 98%
rename from utils/variables/variables.go
rename to utils/variables.go
index 7a0168b4..28f8383a 100644
--- a/utils/variables/variables.go
+++ b/utils/variables.go
@@ -1,4 +1,4 @@
-package variables
+package utils
 
 import (
 	"errors"
diff --git a/utils/variables/variables_test.go b/utils/variables_test.go
similarity index 97%
rename from utils/variables/variables_test.go
rename to utils/variables_test.go
index ec76d459..7122478d 100644
--- a/utils/variables/variables_test.go
+++ b/utils/variables_test.go
@@ -1,4 +1,4 @@
-package variables
+package utils
 
 import "testing"
 

From fe7579966dda1a2e6cec9baa9436f3de0b3cd917 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 21:49:46 +0100
Subject: [PATCH 09/28] Improvements

---
 editor.go => file/editor.go       | 78 +++++++++++--------------------
 info.go => file/info.go           | 43 +++++++----------
 listing.go => file/listing.go     | 12 ++---
 filemanager.go                    | 14 +++---
 frontmatter/runes.go              | 24 ++++++++++
 errors/errors.go => page/error.go | 10 ++--
 preput.go => preprocess.go        |  5 +-
 7 files changed, 90 insertions(+), 96 deletions(-)
 rename editor.go => file/editor.go (50%)
 rename info.go => file/info.go (71%)
 rename listing.go => file/listing.go (94%)
 create mode 100644 frontmatter/runes.go
 rename errors/errors.go => page/error.go (86%)
 rename preput.go => preprocess.go (93%)

diff --git a/editor.go b/file/editor.go
similarity index 50%
rename from editor.go
rename to file/editor.go
index d26efe0d..e0cd3ba8 100644
--- a/editor.go
+++ b/file/editor.go
@@ -1,4 +1,4 @@
-package filemanager
+package file
 
 import (
 	"bytes"
@@ -18,7 +18,7 @@ type Editor struct {
 }
 
 // GetEditor gets the editor based on a FileInfo struct
-func (i *FileInfo) GetEditor() (*Editor, error) {
+func (i *Info) GetEditor() (*Editor, error) {
 	// Create a new editor variable and set the mode
 	editor := new(Editor)
 	editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name()), ".")
@@ -41,44 +41,39 @@ func (i *FileInfo) GetEditor() (*Editor, error) {
 
 	// Handle the content depending on the file extension
 	switch editor.Mode {
-	case "markdown", "asciidoc", "rst":
-		if !hasFrontMatterRune(i.Content) {
-			editor.Class = "content-only"
-			editor.Content = i.StringifyContent()
-			break
-		}
-
-		// Starts a new buffer and parses the file using Hugo's functions
-		buffer := bytes.NewBuffer(i.Content)
-		page, err = parser.ReadFrom(buffer)
-		editor.Class = "complete"
-
-		if err != nil {
-			editor.Class = "content-only"
-			editor.Content = i.StringifyContent()
-			break
-		}
-
-		// Parses the page content and the frontmatter
-		editor.Content = strings.TrimSpace(string(page.Content()))
-		editor.FrontMatter, _, err = frontmatter.Pretty(page.FrontMatter())
 	case "json", "toml", "yaml":
 		// Defines the class and declares an error
 		editor.Class = "frontmatter-only"
 
 		// Checks if the file already has the frontmatter rune and parses it
-		if hasFrontMatterRune(i.Content) {
+		if frontmatter.HasRune(i.Content) {
 			editor.FrontMatter, _, err = frontmatter.Pretty(i.Content)
 		} else {
-			editor.FrontMatter, _, err = frontmatter.Pretty(appendFrontMatterRune(i.Content, editor.Mode))
+			editor.FrontMatter, _, err = frontmatter.Pretty(frontmatter.AppendRune(i.Content, editor.Mode))
 		}
 
 		// Check if there were any errors
-		if err != nil {
-			editor.Class = "content-only"
-			editor.Content = i.StringifyContent()
+		if err == nil {
 			break
 		}
+
+		fallthrough
+	case "markdown", "asciidoc", "rst":
+		if frontmatter.HasRune(i.Content) {
+			// Starts a new buffer and parses the file using Hugo's functions
+			buffer := bytes.NewBuffer(i.Content)
+			page, err = parser.ReadFrom(buffer)
+			editor.Class = "complete"
+
+			if err == nil {
+				// Parses the page content and the frontmatter
+				editor.Content = strings.TrimSpace(string(page.Content()))
+				editor.FrontMatter, _, err = frontmatter.Pretty(page.FrontMatter())
+				break
+			}
+		}
+
+		fallthrough
 	default:
 		editor.Class = "content-only"
 		editor.Content = i.StringifyContent()
@@ -87,29 +82,12 @@ func (i *FileInfo) GetEditor() (*Editor, error) {
 	return editor, nil
 }
 
-// hasFrontMatterRune checks if the file has the frontmatter rune
-func hasFrontMatterRune(file []byte) bool {
-	return strings.HasPrefix(string(file), "---") ||
-		strings.HasPrefix(string(file), "+++") ||
-		strings.HasPrefix(string(file), "{")
-}
-
-// appendFrontMatterRune appends the frontmatter rune to a file
-func appendFrontMatterRune(frontmatter []byte, language string) []byte {
-	switch language {
-	case "yaml":
-		return []byte("---\n" + string(frontmatter) + "\n---")
-	case "toml":
-		return []byte("+++\n" + string(frontmatter) + "\n+++")
-	case "json":
-		return frontmatter
+// CanBeEdited checks if the extension of a file is supported by the editor
+func (i Info) CanBeEdited() bool {
+	if i.Type == "text" {
+		return true
 	}
 
-	return frontmatter
-}
-
-// canBeEdited checks if the extension of a file is supported by the editor
-func canBeEdited(filename string) bool {
 	extensions := [...]string{
 		"md", "markdown", "mdown", "mmark",
 		"asciidoc", "adoc", "ad",
@@ -122,7 +100,7 @@ func canBeEdited(filename string) bool {
 	}
 
 	for _, extension := range extensions {
-		if strings.HasSuffix(filename, extension) {
+		if strings.HasSuffix(i.Name(), extension) {
 			return true
 		}
 	}
diff --git a/info.go b/file/info.go
similarity index 71%
rename from info.go
rename to file/info.go
index 542c13b3..533bae86 100644
--- a/info.go
+++ b/file/info.go
@@ -1,4 +1,4 @@
-package filemanager
+package file
 
 import (
 	"io/ioutil"
@@ -11,10 +11,11 @@ import (
 	humanize "github.com/dustin/go-humanize"
 	"github.com/hacdias/caddy-filemanager/config"
 	"github.com/hacdias/caddy-filemanager/page"
+	"github.com/hacdias/caddy-filemanager/utils"
 )
 
-// FileInfo contains the information about a particular file or directory
-type FileInfo struct {
+// Info contains the information about a particular file or directory
+type Info struct {
 	os.FileInfo
 	URL         string
 	Path        string // Relative path to Caddyfile
@@ -27,10 +28,10 @@ type FileInfo struct {
 
 // GetInfo gets the file information and, in case of error, returns the
 // respective HTTP error code
-func GetInfo(url *url.URL, c *config.Config, u *config.User) (*FileInfo, int, error) {
+func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error) {
 	var err error
 
-	i := &FileInfo{URL: url.Path}
+	i := &Info{URL: url.Path}
 	i.VirtualPath = strings.Replace(url.Path, c.BaseURL, "", 1)
 	i.VirtualPath = strings.TrimPrefix(i.VirtualPath, "/")
 	i.VirtualPath = "/" + i.VirtualPath
@@ -41,50 +42,40 @@ func GetInfo(url *url.URL, c *config.Config, u *config.User) (*FileInfo, int, er
 
 	i.FileInfo, err = os.Stat(i.Path)
 	if err != nil {
-		code := http.StatusInternalServerError
-
-		switch {
-		case os.IsPermission(err):
-			code = http.StatusForbidden
-		case os.IsNotExist(err):
-			code = http.StatusGone
-		case os.IsExist(err):
-			code = http.StatusGone
-		}
-
-		return i, code, err
+		return i, utils.ErrorToHTTPCode(err, false), err
 	}
 
 	return i, 0, nil
 }
 
-func (i *FileInfo) Read() error {
+func (i *Info) Read() error {
 	var err error
 	i.Content, err = ioutil.ReadFile(i.Path)
 	if err != nil {
 		return err
 	}
 	i.Mimetype = http.DetectContentType(i.Content)
-	i.Type = SimplifyMimeType(i.Mimetype)
+	i.Type = simplifyMediaType(i.Mimetype)
 	return nil
 }
 
-func (i FileInfo) StringifyContent() string {
+// StringifyContent returns the string version of Raw
+func (i Info) StringifyContent() string {
 	return string(i.Content)
 }
 
 // HumanSize returns the size of the file as a human-readable string
 // in IEC format (i.e. power of 2 or base 1024).
-func (i FileInfo) HumanSize() string {
+func (i Info) HumanSize() string {
 	return humanize.IBytes(uint64(i.Size()))
 }
 
 // HumanModTime returns the modified time of the file as a human-readable string.
-func (i FileInfo) HumanModTime(format string) string {
+func (i Info) HumanModTime(format string) string {
 	return i.ModTime().Format(format)
 }
 
-func (i *FileInfo) ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+func (i *Info) ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	if i.IsDir() {
 		return i.serveListing(w, r, c, u)
 	}
@@ -92,7 +83,7 @@ func (i *FileInfo) ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.C
 	return i.serveSingleFile(w, r, c, u)
 }
 
-func (i *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	err := i.Read()
 	if err != nil {
 		code := http.StatusInternalServerError
@@ -129,7 +120,7 @@ func (i *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *co
 		},
 	}
 
-	if (canBeEdited(i.Name()) || i.Type == "text") && u.AllowEdit {
+	if i.CanBeEdited() && u.AllowEdit {
 		p.Data, err = i.GetEditor()
 		if err != nil {
 			return http.StatusInternalServerError, err
@@ -141,7 +132,7 @@ func (i *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *co
 	return p.PrintAsHTML(w, "single")
 }
 
-func SimplifyMimeType(name string) string {
+func simplifyMediaType(name string) string {
 	if strings.HasPrefix(name, "video") {
 		return "video"
 	}
diff --git a/listing.go b/file/listing.go
similarity index 94%
rename from listing.go
rename to file/listing.go
index e57be46f..c7e2c7eb 100644
--- a/listing.go
+++ b/file/listing.go
@@ -1,4 +1,4 @@
-package filemanager
+package file
 
 import (
 	"encoding/json"
@@ -25,7 +25,7 @@ type Listing struct {
 	// The full path of the request
 	Path string
 	// The items (files and folders) in the path
-	Items []FileInfo
+	Items []Info
 	// The number of directories in the listing
 	NumDirs int
 	// The number of files (items that aren't directories) in the listing
@@ -149,7 +149,7 @@ func (l Listing) applySort() {
 	}
 }
 
-func (i *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	var err error
 
 	file, err := u.FileSystem.OpenFile(i.VirtualPath, os.O_RDONLY, 0)
@@ -223,7 +223,7 @@ func (i *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *confi
 	return page.PrintAsHTML(w, "listing")
 }
 
-func (i FileInfo) loadDirectoryContents(file http.File, path string, u *config.User) (*Listing, error) {
+func (i Info) loadDirectoryContents(file http.File, path string, u *config.User) (*Listing, error) {
 	files, err := file.Readdir(-1)
 	if err != nil {
 		return nil, err
@@ -235,7 +235,7 @@ func (i FileInfo) loadDirectoryContents(file http.File, path string, u *config.U
 
 func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *config.User) Listing {
 	var (
-		fileinfos           []FileInfo
+		fileinfos           []Info
 		dirCount, fileCount int
 	)
 
@@ -251,7 +251,7 @@ func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *c
 
 		// Absolute URL
 		url := url.URL{Path: basePath + name}
-		fileinfos = append(fileinfos, FileInfo{
+		fileinfos = append(fileinfos, Info{
 			FileInfo:    f,
 			URL:         url.String(),
 			UserAllowed: u.Allowed(url.String()),
diff --git a/filemanager.go b/filemanager.go
index 8532159f..b5826498 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -16,7 +16,7 @@ import (
 
 	"github.com/hacdias/caddy-filemanager/assets"
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/errors"
+	"github.com/hacdias/caddy-filemanager/file"
 	"github.com/hacdias/caddy-filemanager/page"
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
@@ -32,7 +32,7 @@ type FileManager struct {
 func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 	var (
 		c    *config.Config
-		fi   *FileInfo
+		fi   *file.Info
 		code int
 		err  error
 		user *config.User
@@ -71,7 +71,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				}
 
 				if r.Method == http.MethodPut {
-					_, err = fi.Update(w, r, c, user)
+					_, err = processPUT(w, r, c, user, fi)
 					if err != nil {
 						return http.StatusInternalServerError, err
 					}
@@ -83,7 +83,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 			if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.BaseURL)) {
 				if r.Method == http.MethodGet {
-					return errors.PrintHTML(
+					return page.PrintErrorHTML(
 						w,
 						http.StatusForbidden,
 						e.New("You don't have permission to access this page."),
@@ -95,10 +95,10 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 			if r.Method == http.MethodGet {
 				// Gets the information of the directory/file
-				fi, code, err = GetInfo(r.URL, c, user)
+				fi, code, err = file.GetInfo(r.URL, c, user)
 				if err != nil {
 					if r.Method == http.MethodGet {
-						return errors.PrintHTML(w, code, err)
+						return page.PrintErrorHTML(w, code, err)
 					}
 					return code, err
 				}
@@ -131,7 +131,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 				code, err := fi.ServeHTTP(w, r, c, user)
 				if err != nil {
-					return errors.PrintHTML(w, code, err)
+					return page.PrintErrorHTML(w, code, err)
 				}
 				return code, err
 			}
diff --git a/frontmatter/runes.go b/frontmatter/runes.go
new file mode 100644
index 00000000..65d0ddde
--- /dev/null
+++ b/frontmatter/runes.go
@@ -0,0 +1,24 @@
+package frontmatter
+
+import "strings"
+
+// HasRune checks if the file has the frontmatter rune
+func HasRune(file []byte) bool {
+	return strings.HasPrefix(string(file), "---") ||
+		strings.HasPrefix(string(file), "+++") ||
+		strings.HasPrefix(string(file), "{")
+}
+
+// AppendRune appends the frontmatter rune to a file
+func AppendRune(frontmatter []byte, language string) []byte {
+	switch language {
+	case "yaml":
+		return []byte("---\n" + string(frontmatter) + "\n---")
+	case "toml":
+		return []byte("+++\n" + string(frontmatter) + "\n+++")
+	case "json":
+		return frontmatter
+	}
+
+	return frontmatter
+}
diff --git a/errors/errors.go b/page/error.go
similarity index 86%
rename from errors/errors.go
rename to page/error.go
index b859a345..fea2debf 100644
--- a/errors/errors.go
+++ b/page/error.go
@@ -1,4 +1,4 @@
-package errors
+package page
 
 import (
 	"net/http"
@@ -6,7 +6,7 @@ import (
 	"strings"
 )
 
-const template = `<!DOCTYPE html>
+const errTemplate = `<!DOCTYPE html>
 <html>
 <head>
     <title>TITLE</title>
@@ -48,9 +48,9 @@ const template = `<!DOCTYPE html>
     </div>
 </html>`
 
-// PrintHTML prints the error page
-func PrintHTML(w http.ResponseWriter, code int, err error) (int, error) {
-	tpl := template
+// PrintErrorHTML prints the error page
+func PrintErrorHTML(w http.ResponseWriter, code int, err error) (int, error) {
+	tpl := errTemplate
 	tpl = strings.Replace(tpl, "TITLE", strconv.Itoa(code)+" "+http.StatusText(code), -1)
 	tpl = strings.Replace(tpl, "CODE", err.Error(), -1)
 
diff --git a/preput.go b/preprocess.go
similarity index 93%
rename from preput.go
rename to preprocess.go
index f2285910..0d039252 100644
--- a/preput.go
+++ b/preprocess.go
@@ -11,11 +11,12 @@ import (
 	"strings"
 
 	"github.com/hacdias/caddy-filemanager/config"
+	"github.com/hacdias/caddy-filemanager/file"
 	"github.com/spf13/hugo/parser"
 )
 
-// Update is used to update a file that was edited
-func (i *FileInfo) Update(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+// processPUT is used to update a file that was edited
+func processPUT(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) {
 	var (
 		data      map[string]interface{}
 		file      []byte

From 9f9a6254e549854cf6f2b07dce4daa2e54a5ec50 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 18 Oct 2016 22:00:26 +0100
Subject: [PATCH 10/28] Make router prettier

---
 filemanager.go | 207 ++++++++++++++++++++++++++-----------------------
 1 file changed, 109 insertions(+), 98 deletions(-)

diff --git a/filemanager.go b/filemanager.go
index b5826498..e3ac8d32 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -39,128 +39,139 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 	)
 
 	for i := range f.Configs {
-		if httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
-			c = &f.Configs[i]
+		// Checks if this Path should be handled by File Manager.
+		if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
+			return f.Next.ServeHTTP(w, r)
+		}
 
-			if r.Method == http.MethodGet && httpserver.Path(r.URL.Path).Matches(c.BaseURL+assets.BaseURL) {
+		c = &f.Configs[i]
+
+		// Checks if the URL matches the Assets URL. Returns the asset if the
+		// method is GET and Status Forbidden otherwise.
+		if httpserver.Path(r.URL.Path).Matches(c.BaseURL + assets.BaseURL) {
+			if r.Method == http.MethodGet {
 				return assets.Serve(w, r, c)
 			}
 
-			username, _, _ := r.BasicAuth()
+			return http.StatusForbidden, nil
+		}
 
-			if _, ok := c.Users[username]; ok {
-				user = c.Users[username]
-			} else {
-				user = c.User
-			}
-
-			if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
-				if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.WebDavURL)) {
-					return http.StatusForbidden, nil
-				}
-
-				switch r.Method {
-				case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
-					if !user.AllowEdit {
-						return http.StatusForbidden, nil
-					}
-				case "MKCOL", "COPY":
-					if !user.AllowNew {
-						return http.StatusForbidden, nil
-					}
-				}
-
-				if r.Method == http.MethodPut {
-					_, err = processPUT(w, r, c, user, fi)
-					if err != nil {
-						return http.StatusInternalServerError, err
-					}
-				}
-
-				c.Handler.ServeHTTP(w, r)
-				return 0, nil
-			}
-
-			if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.BaseURL)) {
-				if r.Method == http.MethodGet {
-					return page.PrintErrorHTML(
-						w,
-						http.StatusForbidden,
-						e.New("You don't have permission to access this page."),
-					)
-				}
+		// Obtains the user
+		username, _, _ := r.BasicAuth()
+		if _, ok := c.Users[username]; ok {
+			user = c.Users[username]
+		} else {
+			user = c.User
+		}
 
+		// Checks if the request URL is for the WebDav server
+		if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
+			// Checks for user permissions relatively to this PATH
+			if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.WebDavURL)) {
 				return http.StatusForbidden, nil
 			}
 
+			switch r.Method {
+			case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
+				if !user.AllowEdit {
+					return http.StatusForbidden, nil
+				}
+			case "MKCOL", "COPY":
+				if !user.AllowNew {
+					return http.StatusForbidden, nil
+				}
+			}
+
+			// Preprocess the PUT request if it's the case
+			if r.Method == http.MethodPut {
+				_, err = processPUT(w, r, c, user, fi)
+				if err != nil {
+					return http.StatusInternalServerError, err
+				}
+			}
+
+			c.Handler.ServeHTTP(w, r)
+			return 0, nil
+		}
+
+		// Checks if the User is allowed to access this file
+		if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.BaseURL)) {
 			if r.Method == http.MethodGet {
-				// Gets the information of the directory/file
-				fi, code, err = file.GetInfo(r.URL, c, user)
-				if err != nil {
-					if r.Method == http.MethodGet {
-						return page.PrintErrorHTML(w, code, err)
-					}
-					return code, err
-				}
+				return page.PrintErrorHTML(
+					w, http.StatusForbidden,
+					e.New("You don't have permission to access this page."),
+				)
+			}
 
-				// If it's a dir and the path doesn't end with a trailing slash,
-				// redirect the user.
-				if fi.IsDir() && !strings.HasSuffix(r.URL.Path, "/") {
-					http.Redirect(w, r, c.AddrPath+r.URL.Path+"/", http.StatusTemporaryRedirect)
-					return 0, nil
-				}
+			return http.StatusForbidden, nil
+		}
 
-				// Generate anti security token.
-				c.GenerateToken()
-
-				if !fi.IsDir() {
-					query := r.URL.Query()
-					if val, ok := query["raw"]; ok && val[0] == "true" {
-						r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
-						c.Handler.ServeHTTP(w, r)
-						return 0, nil
-					}
-
-					if val, ok := query["download"]; ok && val[0] == "true" {
-						w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name())
-						r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
-						c.Handler.ServeHTTP(w, r)
-						return 0, nil
-					}
-				}
-
-				code, err := fi.ServeHTTP(w, r, c, user)
-				if err != nil {
+		if r.Method == http.MethodGet {
+			// Gets the information of the directory/file
+			fi, code, err = file.GetInfo(r.URL, c, user)
+			if err != nil {
+				if r.Method == http.MethodGet {
 					return page.PrintErrorHTML(w, code, err)
 				}
 				return code, err
 			}
 
-			if r.Method == http.MethodPost {
-				// TODO: How to exclude web dav clients? :/
-				// Security measures against CSRF attacks.
-				if !c.CheckToken(r) {
-					return http.StatusForbidden, nil
+			// If it's a dir and the path doesn't end with a trailing slash,
+			// redirect the user.
+			if fi.IsDir() && !strings.HasSuffix(r.URL.Path, "/") {
+				http.Redirect(w, r, c.AddrPath+r.URL.Path+"/", http.StatusTemporaryRedirect)
+				return 0, nil
+			}
+
+			// Generate anti security token.
+			c.GenerateToken()
+
+			if !fi.IsDir() {
+				query := r.URL.Query()
+				webdav := false
+
+				if val, ok := query["raw"]; ok && val[0] == "true" {
+					webdav = true
 				}
 
-				/* TODO: search commands. USE PROPFIND?
-				// Search and git commands.
-				if r.Header.Get("Search") == "true" {
+				if val, ok := query["download"]; ok && val[0] == "true" {
+					w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name())
+					webdav = true
+				}
 
-				} */
-
-				// VCS commands.
-				if r.Header.Get("Command") != "" {
-					if !user.AllowCommands {
-						return http.StatusUnauthorized, nil
-					}
-
-					return command(w, r, c, user)
+				if webdav {
+					r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
+					c.Handler.ServeHTTP(w, r)
+					return 0, nil
 				}
 			}
 
-			return http.StatusNotImplemented, nil
+			code, err := fi.ServeHTTP(w, r, c, user)
+			if err != nil {
+				return page.PrintErrorHTML(w, code, err)
+			}
+			return code, err
 		}
+
+		if r.Method == http.MethodPost {
+			// TODO: This anti CSCF measure is not being applied to requests
+			// to the WebDav URL namespace. Anyone has ideas?
+			if !c.CheckToken(r) {
+				return http.StatusForbidden, nil
+			}
+
+			// VCS commands.
+			if r.Header.Get("Command") != "" {
+				if !user.AllowCommands {
+					return http.StatusUnauthorized, nil
+				}
+
+				return command(w, r, c, user)
+			}
+		}
+
+		return http.StatusNotImplemented, nil
+
 	}
 
 	return f.Next.ServeHTTP(w, r)

From 3baf7537d9031da485b77030afbd819d8ffdb560 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Wed, 19 Oct 2016 20:46:29 +0100
Subject: [PATCH 11/28] Make sort functions more readable

---
 file/listing.go      | 112 --------------------------------
 file/listing_sort.go | 148 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 148 insertions(+), 112 deletions(-)
 create mode 100644 file/listing_sort.go

diff --git a/file/listing.go b/file/listing.go
index c7e2c7eb..95e30c8d 100644
--- a/file/listing.go
+++ b/file/listing.go
@@ -7,8 +7,6 @@ import (
 	"net/url"
 	"os"
 	"path"
-	"sort"
-	"strconv"
 	"strings"
 
 	"github.com/hacdias/caddy-filemanager/config"
@@ -39,116 +37,6 @@ type Listing struct {
 	httpserver.Context `json:"-"`
 }
 
-// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
-// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
-func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
-	sort, order, limitQuery := r.URL.Query().Get("sort"), r.URL.Query().Get("order"), r.URL.Query().Get("limit")
-
-	// If the query 'sort' or 'order' is empty, use defaults or any values previously saved in Cookies
-	switch sort {
-	case "":
-		sort = "name"
-		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
-			sort = sortCookie.Value
-		}
-	case "name", "size", "type":
-		http.SetCookie(w, &http.Cookie{Name: "sort", Value: sort, Path: scope, Secure: r.TLS != nil})
-	}
-
-	switch order {
-	case "":
-		order = "asc"
-		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
-			order = orderCookie.Value
-		}
-	case "asc", "desc":
-		http.SetCookie(w, &http.Cookie{Name: "order", Value: order, Path: scope, Secure: r.TLS != nil})
-	}
-
-	if limitQuery != "" {
-		limit, err = strconv.Atoi(limitQuery)
-		if err != nil { // if the 'limit' query can't be interpreted as a number, return err
-			return
-		}
-	}
-
-	return
-}
-
-// Implement sorting for Listing
-type byName Listing
-type bySize Listing
-type byTime Listing
-
-// By Name
-func (l byName) Len() int      { return len(l.Items) }
-func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
-
-// Treat upper and lower case equally
-func (l byName) Less(i, j int) bool {
-	if l.Items[i].IsDir() && !l.Items[j].IsDir() {
-		return true
-	}
-
-	if !l.Items[i].IsDir() && l.Items[j].IsDir() {
-		return false
-	}
-
-	return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name())
-}
-
-// By Size
-func (l bySize) Len() int      { return len(l.Items) }
-func (l bySize) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
-
-const directoryOffset = -1 << 31 // = math.MinInt32
-func (l bySize) Less(i, j int) bool {
-	iSize, jSize := l.Items[i].Size(), l.Items[j].Size()
-	if l.Items[i].IsDir() {
-		iSize = directoryOffset + iSize
-	}
-	if l.Items[j].IsDir() {
-		jSize = directoryOffset + jSize
-	}
-	return iSize < jSize
-}
-
-// By Time
-func (l byTime) Len() int           { return len(l.Items) }
-func (l byTime) Swap(i, j int)      { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
-func (l byTime) Less(i, j int) bool { return l.Items[i].ModTime().Before(l.Items[j].ModTime()) }
-
-// Add sorting method to "Listing"
-// it will apply what's in ".Sort" and ".Order"
-func (l Listing) applySort() {
-	// Check '.Order' to know how to sort
-	if l.Order == "desc" {
-		switch l.Sort {
-		case "name":
-			sort.Sort(sort.Reverse(byName(l)))
-		case "size":
-			sort.Sort(sort.Reverse(bySize(l)))
-		case "time":
-			sort.Sort(sort.Reverse(byTime(l)))
-		default:
-			// If not one of the above, do nothing
-			return
-		}
-	} else { // If we had more Orderings we could add them here
-		switch l.Sort {
-		case "name":
-			sort.Sort(byName(l))
-		case "size":
-			sort.Sort(bySize(l))
-		case "time":
-			sort.Sort(byTime(l))
-		default:
-			sort.Sort(byName(l))
-			return
-		}
-	}
-}
-
 func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	var err error
 
diff --git a/file/listing_sort.go b/file/listing_sort.go
new file mode 100644
index 00000000..54076fda
--- /dev/null
+++ b/file/listing_sort.go
@@ -0,0 +1,148 @@
+package file
+
+import (
+	"net/http"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
+// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
+func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
+	sort = r.URL.Query().Get("sort")
+	order = r.URL.Query().Get("order")
+	limitQuery := r.URL.Query().Get("limit")
+
+	// If the query 'sort' or 'order' is empty, use defaults or any values
+	// previously saved in Cookies.
+	switch sort {
+	case "":
+		sort = "name"
+		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
+			sort = sortCookie.Value
+		}
+	case "name", "size", "type":
+		http.SetCookie(w, &http.Cookie{
+			Name:   "sort",
+			Value:  sort,
+			Path:   scope,
+			Secure: r.TLS != nil,
+		})
+	}
+
+	switch order {
+	case "":
+		order = "asc"
+		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
+			order = orderCookie.Value
+		}
+	case "asc", "desc":
+		http.SetCookie(w, &http.Cookie{
+			Name:   "order",
+			Value:  order,
+			Path:   scope,
+			Secure: r.TLS != nil,
+		})
+	}
+
+	if limitQuery != "" {
+		limit, err = strconv.Atoi(limitQuery)
+		// If the 'limit' query can't be interpreted as a number, return err.
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// Add sorting method to "Listing"
+// it will apply what's in ".Sort" and ".Order"
+func (l Listing) applySort() {
+	// Check '.Order' to know how to sort
+	if l.Order == "desc" {
+		switch l.Sort {
+		case "name":
+			sort.Sort(sort.Reverse(byName(l)))
+		case "size":
+			sort.Sort(sort.Reverse(bySize(l)))
+		case "time":
+			sort.Sort(sort.Reverse(byTime(l)))
+		default:
+			// If not one of the above, do nothing
+			return
+		}
+	} else { // If we had more Orderings we could add them here
+		switch l.Sort {
+		case "name":
+			sort.Sort(byName(l))
+		case "size":
+			sort.Sort(bySize(l))
+		case "time":
+			sort.Sort(byTime(l))
+		default:
+			sort.Sort(byName(l))
+			return
+		}
+	}
+}
+
+// Implement sorting for Listing
+type byName Listing
+type bySize Listing
+type byTime Listing
+
+// By Name
+func (l byName) Len() int {
+	return len(l.Items)
+}
+
+func (l byName) Swap(i, j int) {
+	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
+}
+
+// Treat upper and lower case equally
+func (l byName) Less(i, j int) bool {
+	if l.Items[i].IsDir() && !l.Items[j].IsDir() {
+		return true
+	}
+
+	if !l.Items[i].IsDir() && l.Items[j].IsDir() {
+		return false
+	}
+
+	return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name())
+}
+
+// By Size
+func (l bySize) Len() int {
+	return len(l.Items)
+}
+
+func (l bySize) Swap(i, j int) {
+	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
+}
+
+const directoryOffset = -1 << 31 // = math.MinInt32
+func (l bySize) Less(i, j int) bool {
+	iSize, jSize := l.Items[i].Size(), l.Items[j].Size()
+	if l.Items[i].IsDir() {
+		iSize = directoryOffset + iSize
+	}
+	if l.Items[j].IsDir() {
+		jSize = directoryOffset + jSize
+	}
+	return iSize < jSize
+}
+
+// By Time
+func (l byTime) Len() int {
+	return len(l.Items)
+}
+func (l byTime) Swap(i, j int) {
+	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
+}
+func (l byTime) Less(i, j int) bool {
+	return l.Items[i].ModTime().Before(l.Items[j].ModTime())
+}

From 63a1a2cd5457e23763d6ed567a11d643c50c20b1 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Wed, 19 Oct 2016 20:58:08 +0100
Subject: [PATCH 12/28] make everything more readble

---
 file/listing.go | 29 +++++++++--------------------
 1 file changed, 9 insertions(+), 20 deletions(-)

diff --git a/file/listing.go b/file/listing.go
index 95e30c8d..71f0cb71 100644
--- a/file/listing.go
+++ b/file/listing.go
@@ -2,7 +2,6 @@ package file
 
 import (
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"net/url"
 	"os"
@@ -40,23 +39,18 @@ type Listing struct {
 func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
 	var err error
 
+	// Gets the directory information using the Virtual File System of
+	// the user configuration
 	file, err := u.FileSystem.OpenFile(i.VirtualPath, os.O_RDONLY, 0)
 	if err != nil {
 		return utils.ErrorToHTTPCode(err, true), err
 	}
 	defer file.Close()
 
+	// Loads the content of the directory
 	listing, err := i.loadDirectoryContents(file, r.URL.Path, u)
 	if err != nil {
-		fmt.Println(err)
-		switch {
-		case os.IsPermission(err):
-			return http.StatusForbidden, err
-		case os.IsExist(err):
-			return http.StatusGone, err
-		default:
-			return http.StatusInternalServerError, err
-		}
+		return utils.ErrorToHTTPCode(err, true), err
 	}
 
 	listing.Context = httpserver.Context{
@@ -111,17 +105,12 @@ func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Co
 	return page.PrintAsHTML(w, "listing")
 }
 
-func (i Info) loadDirectoryContents(file http.File, path string, u *config.User) (*Listing, error) {
+func (i Info) loadDirectoryContents(file http.File, basePath string, u *config.User) (*Listing, error) {
 	files, err := file.Readdir(-1)
 	if err != nil {
 		return nil, err
 	}
 
-	listing := directoryListing(files, i.VirtualPath, path, u)
-	return &listing, nil
-}
-
-func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *config.User) Listing {
 	var (
 		fileinfos           []Info
 		dirCount, fileCount int
@@ -146,11 +135,11 @@ func directoryListing(files []os.FileInfo, urlPath string, basePath string, u *c
 		})
 	}
 
-	return Listing{
-		Name:     path.Base(urlPath),
-		Path:     urlPath,
+	return &Listing{
+		Name:     path.Base(i.VirtualPath),
+		Path:     i.VirtualPath,
 		Items:    fileinfos,
 		NumDirs:  dirCount,
 		NumFiles: fileCount,
-	}
+	}, nil
 }

From 3379e6e67c062d87d3634e7ce6814abad19e733e Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Thu, 20 Oct 2016 21:55:55 +0100
Subject: [PATCH 13/28] add support to download directories as zip, tar, targz
 and tarbz2 #29

---
 file/download.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++
 filemanager.go   |  6 ++++++
 2 files changed, 55 insertions(+)
 create mode 100644 file/download.go

diff --git a/file/download.go b/file/download.go
new file mode 100644
index 00000000..e46bbbca
--- /dev/null
+++ b/file/download.go
@@ -0,0 +1,49 @@
+package file
+
+import (
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+
+	"github.com/mholt/archiver"
+)
+
+func (i *Info) DownloadAs(w http.ResponseWriter, query string) (int, error) {
+	var (
+		extension string
+		temp      string
+		err       error
+	)
+
+	temp, err = ioutil.TempDir("", "")
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	switch query {
+	case "zip":
+		extension, err = ".zip", archiver.Zip.Make(temp+"/temp", []string{i.Path})
+	case "tar":
+		extension, err = ".tar", archiver.Tar.Make(temp+"/temp", []string{i.Path})
+	case "targz":
+		extension, err = ".tar.gz", archiver.TarGz.Make(temp+"/temp", []string{i.Path})
+	case "tarbz2":
+		extension, err = ".tar.bz2", archiver.TarBz2.Make(temp+"/temp", []string{i.Path})
+	default:
+		return http.StatusNotImplemented, nil
+	}
+
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	file, err := os.Open(temp + "/temp")
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	w.Header().Set("Content-Disposition", "attachment; filename="+i.Name()+extension)
+	io.Copy(w, file)
+	return http.StatusOK, nil
+}
diff --git a/filemanager.go b/filemanager.go
index e3ac8d32..a915ba5b 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -126,6 +126,12 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 			// Generate anti security token.
 			c.GenerateToken()
 
+			if fi.IsDir() {
+				if val, ok := r.URL.Query()["download"]; ok && val[0] != "" {
+					return fi.DownloadAs(w, val[0])
+				}
+			}
+
 			if !fi.IsDir() {
 				query := r.URL.Query()
 				webdav := false

From 9453dc246a1ee2772cddeb2029e5a1894812d47f Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Thu, 20 Oct 2016 21:58:53 +0100
Subject: [PATCH 14/28] updates on #29

---
 file/download.go | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/file/download.go b/file/download.go
index e46bbbca..ce751e3f 100644
--- a/file/download.go
+++ b/file/download.go
@@ -5,15 +5,19 @@ import (
 	"io/ioutil"
 	"net/http"
 	"os"
+	"path/filepath"
 
 	"github.com/mholt/archiver"
 )
 
+// DownloadAs creates an archieve in one of the supported formats (zip, tar,
+// tar.gz or tar.bz2) and sends it to be downloaded.
 func (i *Info) DownloadAs(w http.ResponseWriter, query string) (int, error) {
 	var (
 		extension string
 		temp      string
 		err       error
+		tempfile  string
 	)
 
 	temp, err = ioutil.TempDir("", "")
@@ -21,15 +25,18 @@ func (i *Info) DownloadAs(w http.ResponseWriter, query string) (int, error) {
 		return http.StatusInternalServerError, err
 	}
 
+	defer os.RemoveAll(temp)
+	tempfile = filepath.Join(temp, "temp")
+
 	switch query {
 	case "zip":
-		extension, err = ".zip", archiver.Zip.Make(temp+"/temp", []string{i.Path})
+		extension, err = ".zip", archiver.Zip.Make(tempfile, []string{i.Path})
 	case "tar":
-		extension, err = ".tar", archiver.Tar.Make(temp+"/temp", []string{i.Path})
+		extension, err = ".tar", archiver.Tar.Make(tempfile, []string{i.Path})
 	case "targz":
-		extension, err = ".tar.gz", archiver.TarGz.Make(temp+"/temp", []string{i.Path})
+		extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, []string{i.Path})
 	case "tarbz2":
-		extension, err = ".tar.bz2", archiver.TarBz2.Make(temp+"/temp", []string{i.Path})
+		extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, []string{i.Path})
 	default:
 		return http.StatusNotImplemented, nil
 	}

From 591d5b3084d75ad8663bf2399cde2fa3b6830bec Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Thu, 20 Oct 2016 22:18:45 +0100
Subject: [PATCH 15/28] css fix word-wrap on command result

---
 assets/embed/public/css/styles.css | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/assets/embed/public/css/styles.css b/assets/embed/public/css/styles.css
index bb21074c..ce534396 100644
--- a/assets/embed/public/css/styles.css
+++ b/assets/embed/public/css/styles.css
@@ -566,6 +566,7 @@ header p i {
     transition: .1s ease all;
     visibility: hidden;
     opacity: 0;
+    word-wrap: break-word;
 }
 
 #search.active div i,
@@ -1166,4 +1167,4 @@ i.spin {
         column-count: 1;
         column-gap: 0;
     }
-}
+}
\ No newline at end of file

From 1b387d215f809760df7f30ddc41385cd8aba5094 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Thu, 20 Oct 2016 22:23:36 +0100
Subject: [PATCH 16/28] Progresses on #23. Support differnt webdav urls

---
 assets/embed/public/js/application.js | 4 ++--
 assets/embed/templates/base.tmpl      | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/assets/embed/public/js/application.js b/assets/embed/public/js/application.js
index a4e2b86f..12c73c70 100644
--- a/assets/embed/public/js/application.js
+++ b/assets/embed/public/js/application.js
@@ -89,7 +89,7 @@ Element.prototype.changeToDone = function(error, html) {
 }
 
 var toWebDavURL = function(url) {
-    url = url.replace("/", "/webdav/")
+    url = url.replace(baseURL + "/", webdavURL + "/");
     return window.location.origin + url
 }
 
@@ -908,4 +908,4 @@ document.addEventListener("DOMContentLoaded", function(event) {
     }
 
     return false;
-});
+});
\ No newline at end of file
diff --git a/assets/embed/templates/base.tmpl b/assets/embed/templates/base.tmpl
index 7acb1bab..86f4e844 100644
--- a/assets/embed/templates/base.tmpl
+++ b/assets/embed/templates/base.tmpl
@@ -129,8 +129,8 @@
   </footer>
 
   <!-- SCRIPTS -->
-  <!-- User Data and Permissions -->
-  <script>var user = JSON.parse('{{ Marshal .User }}');</script>
+  <!-- User Data and Permissions; WebDavURL -->
+  <script>var user = JSON.parse('{{ Marshal .User }}'), webdavURL = "{{.Config.WebDavURL}}", baseURL = "{{.Config.BaseURL}}";</script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js"></script>
   <script src="{{ .Config.AbsoluteURL }}/_filemanagerinternal/js/form2js.js"></script>
   <script src="{{ .Config.AbsoluteURL }}/_filemanagerinternal/js/application.js"></script>

From 212bba2c376fef38dd05b60601c5bf4dd6afd033 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Thu, 20 Oct 2016 22:45:43 +0100
Subject: [PATCH 17/28] improve styles; on hover not really working

---
 assets/embed/public/css/styles.css | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/assets/embed/public/css/styles.css b/assets/embed/public/css/styles.css
index ce534396..644d9cec 100644
--- a/assets/embed/public/css/styles.css
+++ b/assets/embed/public/css/styles.css
@@ -687,14 +687,14 @@ header .prev-links:hover {
 
 header .prev-links {
     position: absolute;
-    top: 0;
+    top: 4em;
     left: 0;
     color: #7d7d7d;
     list-style: none;
     margin: 0;
     padding: 0;
     background: #fff;
-    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
+    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
     border-radius: .2em;
     flex-direction: column-reverse;
     display: none;
@@ -702,6 +702,16 @@ header .prev-links {
     min-width: 12em;
 }
 
+header .prev-links:before {
+    top: -16px;
+    left: 1em;
+    right: auto;
+    border: 8px solid transparent;
+    border-bottom-color: #68efad;
+    content: '';
+    position: absolute;
+}
+
 header .prev-links a {
     padding: .5em;
     border-bottom: 1px solid #f5f5f5;
@@ -1167,4 +1177,4 @@ i.spin {
         column-count: 1;
         column-gap: 0;
     }
-}
\ No newline at end of file
+}

From 4c8e023e9f8490940d130aa68958ae844eb7368a Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 09:19:08 +0100
Subject: [PATCH 18/28] update

---
 file/listing.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/file/listing.go b/file/listing.go
index 71f0cb71..757a9080 100644
--- a/file/listing.go
+++ b/file/listing.go
@@ -131,7 +131,7 @@ func (i Info) loadDirectoryContents(file http.File, basePath string, u *config.U
 		fileinfos = append(fileinfos, Info{
 			FileInfo:    f,
 			URL:         url.String(),
-			UserAllowed: u.Allowed(url.String()),
+			UserAllowed: u.Allowed(i.VirtualPath),
 		})
 	}
 

From ae338251823f1a0b101ce5ce4dd327405b12c13b Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 11:47:49 +0100
Subject: [PATCH 19/28] organise stuff better

---
 assets/embed/templates/single.tmpl |   4 +-
 file/download.go                   |  55 ---------
 file/info.go                       |  58 ----------
 file/listing.go                    | 175 +++++++++++++++++------------
 file/listing_sort.go               | 148 ------------------------
 filemanager.go                     |  80 +++----------
 handlers/command.go                |  48 ++++++++
 handlers/download.go               |  70 ++++++++++++
 handlers/listing.go                | 123 ++++++++++++++++++++
 handlers/single.go                 |  41 +++++++
 preprocess.go                      |   8 +-
 11 files changed, 407 insertions(+), 403 deletions(-)
 delete mode 100644 file/listing_sort.go
 create mode 100644 handlers/command.go
 create mode 100644 handlers/download.go
 create mode 100644 handlers/listing.go
 create mode 100644 handlers/single.go

diff --git a/assets/embed/templates/single.tmpl b/assets/embed/templates/single.tmpl
index d70d1646..f360663e 100644
--- a/assets/embed/templates/single.tmpl
+++ b/assets/embed/templates/single.tmpl
@@ -6,7 +6,9 @@
    {{ else if eq .Type "audio" }}
    <audio src="{{ .URL }}?raw=true"></audio>
    {{ else if eq .Type "video" }}
-
+   <!-- TODO: SHOW VIDEO ? -->
+   {{ else if eq .Type "blob" }}
+   <a href="?download=true">Download</a>
    {{ else}}
    <pre>{{ .StringifyContent }}</pre>
   {{ end }}
diff --git a/file/download.go b/file/download.go
index ce751e3f..b691ba57 100644
--- a/file/download.go
+++ b/file/download.go
@@ -1,56 +1 @@
 package file
-
-import (
-	"io"
-	"io/ioutil"
-	"net/http"
-	"os"
-	"path/filepath"
-
-	"github.com/mholt/archiver"
-)
-
-// DownloadAs creates an archieve in one of the supported formats (zip, tar,
-// tar.gz or tar.bz2) and sends it to be downloaded.
-func (i *Info) DownloadAs(w http.ResponseWriter, query string) (int, error) {
-	var (
-		extension string
-		temp      string
-		err       error
-		tempfile  string
-	)
-
-	temp, err = ioutil.TempDir("", "")
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-
-	defer os.RemoveAll(temp)
-	tempfile = filepath.Join(temp, "temp")
-
-	switch query {
-	case "zip":
-		extension, err = ".zip", archiver.Zip.Make(tempfile, []string{i.Path})
-	case "tar":
-		extension, err = ".tar", archiver.Tar.Make(tempfile, []string{i.Path})
-	case "targz":
-		extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, []string{i.Path})
-	case "tarbz2":
-		extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, []string{i.Path})
-	default:
-		return http.StatusNotImplemented, nil
-	}
-
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-
-	file, err := os.Open(temp + "/temp")
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-
-	w.Header().Set("Content-Disposition", "attachment; filename="+i.Name()+extension)
-	io.Copy(w, file)
-	return http.StatusOK, nil
-}
diff --git a/file/info.go b/file/info.go
index 533bae86..9ffaa66d 100644
--- a/file/info.go
+++ b/file/info.go
@@ -10,7 +10,6 @@ import (
 
 	humanize "github.com/dustin/go-humanize"
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/page"
 	"github.com/hacdias/caddy-filemanager/utils"
 )
 
@@ -75,63 +74,6 @@ func (i Info) HumanModTime(format string) string {
 	return i.ModTime().Format(format)
 }
 
-func (i *Info) ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	if i.IsDir() {
-		return i.serveListing(w, r, c, u)
-	}
-
-	return i.serveSingleFile(w, r, c, u)
-}
-
-func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	err := i.Read()
-	if err != nil {
-		code := http.StatusInternalServerError
-
-		switch {
-		case os.IsPermission(err):
-			code = http.StatusForbidden
-		case os.IsNotExist(err):
-			code = http.StatusGone
-		case os.IsExist(err):
-			code = http.StatusGone
-		}
-
-		return code, err
-	}
-
-	if i.Type == "blob" {
-		http.Redirect(
-			w, r,
-			c.AddrPath+r.URL.Path+"?download=true",
-			http.StatusTemporaryRedirect,
-		)
-		return 0, nil
-	}
-
-	p := &page.Page{
-		Info: &page.Info{
-			Name:   i.Name(),
-			Path:   i.VirtualPath,
-			IsDir:  false,
-			Data:   i,
-			User:   u,
-			Config: c,
-		},
-	}
-
-	if i.CanBeEdited() && u.AllowEdit {
-		p.Data, err = i.GetEditor()
-		if err != nil {
-			return http.StatusInternalServerError, err
-		}
-
-		return p.PrintAsHTML(w, "frontmatter", "editor")
-	}
-
-	return p.PrintAsHTML(w, "single")
-}
-
 func simplifyMediaType(name string) string {
 	if strings.HasPrefix(name, "video") {
 		return "video"
diff --git a/file/listing.go b/file/listing.go
index 757a9080..91ff2b1a 100644
--- a/file/listing.go
+++ b/file/listing.go
@@ -1,16 +1,13 @@
 package file
 
 import (
-	"encoding/json"
-	"net/http"
 	"net/url"
 	"os"
 	"path"
+	"sort"
 	"strings"
 
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/page"
-	"github.com/hacdias/caddy-filemanager/utils"
 
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
@@ -19,7 +16,7 @@ import (
 type Listing struct {
 	// The name of the directory (the last element of the path)
 	Name string
-	// The full path of the request
+	// The full path of the request relatively to a File System
 	Path string
 	// The items (files and folders) in the path
 	Items []Info
@@ -36,76 +33,17 @@ type Listing struct {
 	httpserver.Context `json:"-"`
 }
 
-func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	var err error
-
+// GetListing gets the information about a specific directory and its files.
+func GetListing(u *config.User, filePath string, baseURL string) (*Listing, error) {
 	// Gets the directory information using the Virtual File System of
-	// the user configuration
-	file, err := u.FileSystem.OpenFile(i.VirtualPath, os.O_RDONLY, 0)
+	// the user configuration.
+	file, err := u.FileSystem.OpenFile(filePath, os.O_RDONLY, 0)
 	if err != nil {
-		return utils.ErrorToHTTPCode(err, true), err
+		return nil, err
 	}
 	defer file.Close()
 
-	// Loads the content of the directory
-	listing, err := i.loadDirectoryContents(file, r.URL.Path, u)
-	if err != nil {
-		return utils.ErrorToHTTPCode(err, true), err
-	}
-
-	listing.Context = httpserver.Context{
-		Root: http.Dir(u.Scope),
-		Req:  r,
-		URL:  r.URL,
-	}
-
-	// Copy the query values into the Listing struct
-	var limit int
-	listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.Scope)
-	if err != nil {
-		return http.StatusBadRequest, err
-	}
-
-	listing.applySort()
-
-	if limit > 0 && limit <= len(listing.Items) {
-		listing.Items = listing.Items[:limit]
-		listing.ItemsLimitedTo = limit
-	}
-
-	if strings.Contains(r.Header.Get("Accept"), "application/json") {
-		marsh, err := json.Marshal(listing.Items)
-		if err != nil {
-			return http.StatusInternalServerError, err
-		}
-
-		w.Header().Set("Content-Type", "application/json; charset=utf-8")
-		if _, err := w.Write(marsh); err != nil {
-			return http.StatusInternalServerError, err
-		}
-
-		return http.StatusOK, nil
-	}
-
-	page := &page.Page{
-		Info: &page.Info{
-			Name:   listing.Name,
-			Path:   i.VirtualPath,
-			IsDir:  true,
-			User:   u,
-			Config: c,
-			Data:   listing,
-		},
-	}
-
-	if r.Header.Get("Minimal") == "true" {
-		page.Minimal = true
-	}
-
-	return page.PrintAsHTML(w, "listing")
-}
-
-func (i Info) loadDirectoryContents(file http.File, basePath string, u *config.User) (*Listing, error) {
+	// Reads the directory and gets the information about the files.
 	files, err := file.Readdir(-1)
 	if err != nil {
 		return nil, err
@@ -127,19 +65,108 @@ func (i Info) loadDirectoryContents(file http.File, basePath string, u *config.U
 		}
 
 		// Absolute URL
-		url := url.URL{Path: basePath + name}
+		url := url.URL{Path: baseURL + name}
 		fileinfos = append(fileinfos, Info{
 			FileInfo:    f,
 			URL:         url.String(),
-			UserAllowed: u.Allowed(i.VirtualPath),
+			UserAllowed: u.Allowed(filePath),
 		})
 	}
 
 	return &Listing{
-		Name:     path.Base(i.VirtualPath),
-		Path:     i.VirtualPath,
+		Name:     path.Base(filePath),
+		Path:     filePath,
 		Items:    fileinfos,
 		NumDirs:  dirCount,
 		NumFiles: fileCount,
 	}, nil
 }
+
+// ApplySort applies the sort order using .Order and .Sort
+func (l Listing) ApplySort() {
+	// Check '.Order' to know how to sort
+	if l.Order == "desc" {
+		switch l.Sort {
+		case "name":
+			sort.Sort(sort.Reverse(byName(l)))
+		case "size":
+			sort.Sort(sort.Reverse(bySize(l)))
+		case "time":
+			sort.Sort(sort.Reverse(byTime(l)))
+		default:
+			// If not one of the above, do nothing
+			return
+		}
+	} else { // If we had more Orderings we could add them here
+		switch l.Sort {
+		case "name":
+			sort.Sort(byName(l))
+		case "size":
+			sort.Sort(bySize(l))
+		case "time":
+			sort.Sort(byTime(l))
+		default:
+			sort.Sort(byName(l))
+			return
+		}
+	}
+}
+
+// Implement sorting for Listing
+type byName Listing
+type bySize Listing
+type byTime Listing
+
+// By Name
+func (l byName) Len() int {
+	return len(l.Items)
+}
+
+func (l byName) Swap(i, j int) {
+	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
+}
+
+// Treat upper and lower case equally
+func (l byName) Less(i, j int) bool {
+	if l.Items[i].IsDir() && !l.Items[j].IsDir() {
+		return true
+	}
+
+	if !l.Items[i].IsDir() && l.Items[j].IsDir() {
+		return false
+	}
+
+	return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name())
+}
+
+// By Size
+func (l bySize) Len() int {
+	return len(l.Items)
+}
+
+func (l bySize) Swap(i, j int) {
+	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
+}
+
+const directoryOffset = -1 << 31 // = math.MinInt32
+func (l bySize) Less(i, j int) bool {
+	iSize, jSize := l.Items[i].Size(), l.Items[j].Size()
+	if l.Items[i].IsDir() {
+		iSize = directoryOffset + iSize
+	}
+	if l.Items[j].IsDir() {
+		jSize = directoryOffset + jSize
+	}
+	return iSize < jSize
+}
+
+// By Time
+func (l byTime) Len() int {
+	return len(l.Items)
+}
+func (l byTime) Swap(i, j int) {
+	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
+}
+func (l byTime) Less(i, j int) bool {
+	return l.Items[i].ModTime().Before(l.Items[j].ModTime())
+}
diff --git a/file/listing_sort.go b/file/listing_sort.go
deleted file mode 100644
index 54076fda..00000000
--- a/file/listing_sort.go
+++ /dev/null
@@ -1,148 +0,0 @@
-package file
-
-import (
-	"net/http"
-	"sort"
-	"strconv"
-	"strings"
-)
-
-// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
-// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
-func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
-	sort = r.URL.Query().Get("sort")
-	order = r.URL.Query().Get("order")
-	limitQuery := r.URL.Query().Get("limit")
-
-	// If the query 'sort' or 'order' is empty, use defaults or any values
-	// previously saved in Cookies.
-	switch sort {
-	case "":
-		sort = "name"
-		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
-			sort = sortCookie.Value
-		}
-	case "name", "size", "type":
-		http.SetCookie(w, &http.Cookie{
-			Name:   "sort",
-			Value:  sort,
-			Path:   scope,
-			Secure: r.TLS != nil,
-		})
-	}
-
-	switch order {
-	case "":
-		order = "asc"
-		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
-			order = orderCookie.Value
-		}
-	case "asc", "desc":
-		http.SetCookie(w, &http.Cookie{
-			Name:   "order",
-			Value:  order,
-			Path:   scope,
-			Secure: r.TLS != nil,
-		})
-	}
-
-	if limitQuery != "" {
-		limit, err = strconv.Atoi(limitQuery)
-		// If the 'limit' query can't be interpreted as a number, return err.
-		if err != nil {
-			return
-		}
-	}
-
-	return
-}
-
-// Add sorting method to "Listing"
-// it will apply what's in ".Sort" and ".Order"
-func (l Listing) applySort() {
-	// Check '.Order' to know how to sort
-	if l.Order == "desc" {
-		switch l.Sort {
-		case "name":
-			sort.Sort(sort.Reverse(byName(l)))
-		case "size":
-			sort.Sort(sort.Reverse(bySize(l)))
-		case "time":
-			sort.Sort(sort.Reverse(byTime(l)))
-		default:
-			// If not one of the above, do nothing
-			return
-		}
-	} else { // If we had more Orderings we could add them here
-		switch l.Sort {
-		case "name":
-			sort.Sort(byName(l))
-		case "size":
-			sort.Sort(bySize(l))
-		case "time":
-			sort.Sort(byTime(l))
-		default:
-			sort.Sort(byName(l))
-			return
-		}
-	}
-}
-
-// Implement sorting for Listing
-type byName Listing
-type bySize Listing
-type byTime Listing
-
-// By Name
-func (l byName) Len() int {
-	return len(l.Items)
-}
-
-func (l byName) Swap(i, j int) {
-	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
-}
-
-// Treat upper and lower case equally
-func (l byName) Less(i, j int) bool {
-	if l.Items[i].IsDir() && !l.Items[j].IsDir() {
-		return true
-	}
-
-	if !l.Items[i].IsDir() && l.Items[j].IsDir() {
-		return false
-	}
-
-	return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name())
-}
-
-// By Size
-func (l bySize) Len() int {
-	return len(l.Items)
-}
-
-func (l bySize) Swap(i, j int) {
-	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
-}
-
-const directoryOffset = -1 << 31 // = math.MinInt32
-func (l bySize) Less(i, j int) bool {
-	iSize, jSize := l.Items[i].Size(), l.Items[j].Size()
-	if l.Items[i].IsDir() {
-		iSize = directoryOffset + iSize
-	}
-	if l.Items[j].IsDir() {
-		jSize = directoryOffset + jSize
-	}
-	return iSize < jSize
-}
-
-// By Time
-func (l byTime) Len() int {
-	return len(l.Items)
-}
-func (l byTime) Swap(i, j int) {
-	l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
-}
-func (l byTime) Less(i, j int) bool {
-	return l.Items[i].ModTime().Before(l.Items[j].ModTime())
-}
diff --git a/filemanager.go b/filemanager.go
index a915ba5b..23d31bbd 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -10,13 +10,12 @@ package filemanager
 import (
 	e "errors"
 	"net/http"
-	"os/exec"
-	"path/filepath"
 	"strings"
 
 	"github.com/hacdias/caddy-filemanager/assets"
 	"github.com/hacdias/caddy-filemanager/config"
 	"github.com/hacdias/caddy-filemanager/file"
+	"github.com/hacdias/caddy-filemanager/handlers"
 	"github.com/hacdias/caddy-filemanager/page"
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
@@ -126,36 +125,22 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 			// Generate anti security token.
 			c.GenerateToken()
 
-			if fi.IsDir() {
-				if val, ok := r.URL.Query()["download"]; ok && val[0] != "" {
-					return fi.DownloadAs(w, val[0])
-				}
+			switch {
+			case r.URL.Query().Get("download") != "":
+				code, err = handlers.Download(w, r, c, fi)
+			case r.URL.Query().Get("raw") == "true" && !fi.IsDir():
+				http.ServeFile(w, r, fi.Path)
+				code, err = 0, nil
+			case fi.IsDir():
+				code, err = handlers.ServeListing(w, r, c, user, fi)
+			default:
+				code, err = handlers.ServeSingle(w, r, c, user, fi)
 			}
 
-			if !fi.IsDir() {
-				query := r.URL.Query()
-				webdav := false
-
-				if val, ok := query["raw"]; ok && val[0] == "true" {
-					webdav = true
-				}
-
-				if val, ok := query["download"]; ok && val[0] == "true" {
-					w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name())
-					webdav = true
-				}
-
-				if webdav {
-					r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1)
-					c.Handler.ServeHTTP(w, r)
-					return 0, nil
-				}
-			}
-
-			code, err := fi.ServeHTTP(w, r, c, user)
 			if err != nil {
-				return page.PrintErrorHTML(w, code, err)
+				code, err = page.PrintErrorHTML(w, code, err)
 			}
+
 			return code, err
 		}
 
@@ -172,7 +157,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 					return http.StatusUnauthorized, nil
 				}
 
-				return command(w, r, c, user)
+				return handlers.Command(w, r, c, user)
 			}
 		}
 
@@ -182,40 +167,3 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 	return f.Next.ServeHTTP(w, r)
 }
-
-// command handles the requests for VCS related commands: git, svn and mercurial
-func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
-	command := strings.Split(r.Header.Get("command"), " ")
-
-	// Check if the command is allowed
-	mayContinue := false
-
-	for _, cmd := range u.Commands {
-		if cmd == command[0] {
-			mayContinue = true
-		}
-	}
-
-	if !mayContinue {
-		return http.StatusForbidden, nil
-	}
-
-	// Check if the program is talled is installed on the computer
-	if _, err := exec.LookPath(command[0]); err != nil {
-		return http.StatusNotImplemented, nil
-	}
-
-	path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1)
-	path = filepath.Clean(path)
-
-	cmd := exec.Command(command[0], command[1:len(command)]...)
-	cmd.Dir = path
-	output, err := cmd.CombinedOutput()
-
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-
-	p := &page.Page{Info: &page.Info{Data: string(output)}}
-	return p.PrintAsJSON(w)
-}
diff --git a/handlers/command.go b/handlers/command.go
new file mode 100644
index 00000000..e2690c42
--- /dev/null
+++ b/handlers/command.go
@@ -0,0 +1,48 @@
+package handlers
+
+import (
+	"net/http"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	"github.com/hacdias/caddy-filemanager/config"
+	"github.com/hacdias/caddy-filemanager/page"
+)
+
+// Command handles the requests for VCS related commands: git, svn and mercurial
+func Command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) {
+	command := strings.Split(r.Header.Get("command"), " ")
+
+	// Check if the command is allowed
+	mayContinue := false
+
+	for _, cmd := range u.Commands {
+		if cmd == command[0] {
+			mayContinue = true
+		}
+	}
+
+	if !mayContinue {
+		return http.StatusForbidden, nil
+	}
+
+	// Check if the program is talled is installed on the computer
+	if _, err := exec.LookPath(command[0]); err != nil {
+		return http.StatusNotImplemented, nil
+	}
+
+	path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1)
+	path = filepath.Clean(path)
+
+	cmd := exec.Command(command[0], command[1:len(command)]...)
+	cmd.Dir = path
+	output, err := cmd.CombinedOutput()
+
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	p := &page.Page{Info: &page.Info{Data: string(output)}}
+	return p.PrintAsJSON(w)
+}
diff --git a/handlers/download.go b/handlers/download.go
new file mode 100644
index 00000000..f4fc95a2
--- /dev/null
+++ b/handlers/download.go
@@ -0,0 +1,70 @@
+package handlers
+
+import (
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+
+	"github.com/hacdias/caddy-filemanager/config"
+	"github.com/hacdias/caddy-filemanager/file"
+	"github.com/mholt/archiver"
+)
+
+// Download creates an archieve in one of the supported formats (zip, tar,
+// tar.gz or tar.bz2) and sends it to be downloaded.
+func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.Info) (int, error) {
+	query := r.URL.Query().Get("download")
+
+	if !i.IsDir() {
+		w.Header().Set("Content-Disposition", "attachment; filename="+i.Name())
+		http.ServeFile(w, r, i.Path)
+		return 0, nil
+	}
+
+	if query == "true" {
+		query = "zip"
+	}
+
+	var (
+		extension string
+		temp      string
+		err       error
+		tempfile  string
+	)
+
+	temp, err = ioutil.TempDir("", "")
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	defer os.RemoveAll(temp)
+	tempfile = filepath.Join(temp, "temp")
+
+	switch query {
+	case "zip":
+		extension, err = ".zip", archiver.Zip.Make(tempfile, []string{i.Path})
+	case "tar":
+		extension, err = ".tar", archiver.Tar.Make(tempfile, []string{i.Path})
+	case "targz":
+		extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, []string{i.Path})
+	case "tarbz2":
+		extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, []string{i.Path})
+	default:
+		return http.StatusNotImplemented, nil
+	}
+
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	file, err := os.Open(temp + "/temp")
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	w.Header().Set("Content-Disposition", "attachment; filename="+i.Name()+extension)
+	io.Copy(w, file)
+	return http.StatusOK, nil
+}
diff --git a/handlers/listing.go b/handlers/listing.go
new file mode 100644
index 00000000..13b36d33
--- /dev/null
+++ b/handlers/listing.go
@@ -0,0 +1,123 @@
+package handlers
+
+import (
+	"encoding/json"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/hacdias/caddy-filemanager/config"
+	"github.com/hacdias/caddy-filemanager/file"
+	"github.com/hacdias/caddy-filemanager/page"
+	"github.com/hacdias/caddy-filemanager/utils"
+	"github.com/mholt/caddy/caddyhttp/httpserver"
+)
+
+// ServeListing presents the user with a listage of a directory folder.
+func ServeListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) {
+	var err error
+
+	// Loads the content of the directory
+	listing, err := file.GetListing(u, i.VirtualPath, r.URL.Path)
+	if err != nil {
+		return utils.ErrorToHTTPCode(err, true), err
+	}
+
+	listing.Context = httpserver.Context{
+		Root: http.Dir(u.Scope),
+		Req:  r,
+		URL:  r.URL,
+	}
+
+	// Copy the query values into the Listing struct
+	var limit int
+	listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.Scope)
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	listing.ApplySort()
+
+	if limit > 0 && limit <= len(listing.Items) {
+		listing.Items = listing.Items[:limit]
+		listing.ItemsLimitedTo = limit
+	}
+
+	if strings.Contains(r.Header.Get("Accept"), "application/json") {
+		marsh, err := json.Marshal(listing.Items)
+		if err != nil {
+			return http.StatusInternalServerError, err
+		}
+
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		if _, err := w.Write(marsh); err != nil {
+			return http.StatusInternalServerError, err
+		}
+
+		return http.StatusOK, nil
+	}
+
+	page := &page.Page{
+		Minimal: r.Header.Get("Minimal") == "true",
+		Info: &page.Info{
+			Name:   listing.Name,
+			Path:   i.VirtualPath,
+			IsDir:  true,
+			User:   u,
+			Config: c,
+			Data:   listing,
+		},
+	}
+
+	return page.PrintAsHTML(w, "listing")
+}
+
+// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
+// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
+func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
+	sort = r.URL.Query().Get("sort")
+	order = r.URL.Query().Get("order")
+	limitQuery := r.URL.Query().Get("limit")
+
+	// If the query 'sort' or 'order' is empty, use defaults or any values
+	// previously saved in Cookies.
+	switch sort {
+	case "":
+		sort = "name"
+		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
+			sort = sortCookie.Value
+		}
+	case "name", "size", "type":
+		http.SetCookie(w, &http.Cookie{
+			Name:   "sort",
+			Value:  sort,
+			Path:   scope,
+			Secure: r.TLS != nil,
+		})
+	}
+
+	switch order {
+	case "":
+		order = "asc"
+		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
+			order = orderCookie.Value
+		}
+	case "asc", "desc":
+		http.SetCookie(w, &http.Cookie{
+			Name:   "order",
+			Value:  order,
+			Path:   scope,
+			Secure: r.TLS != nil,
+		})
+	}
+
+	if limitQuery != "" {
+		limit, err = strconv.Atoi(limitQuery)
+		// If the 'limit' query can't be interpreted as a number, return err.
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
diff --git a/handlers/single.go b/handlers/single.go
new file mode 100644
index 00000000..93dd3b85
--- /dev/null
+++ b/handlers/single.go
@@ -0,0 +1,41 @@
+package handlers
+
+import (
+	"net/http"
+
+	"github.com/hacdias/caddy-filemanager/config"
+	"github.com/hacdias/caddy-filemanager/file"
+	"github.com/hacdias/caddy-filemanager/page"
+	"github.com/hacdias/caddy-filemanager/utils"
+)
+
+// ServeSingle serves a single file in an editor (if it is editable), shows the
+// plain file, or downloads it if it can't be shown.
+func ServeSingle(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) {
+	err := i.Read()
+	if err != nil {
+		return utils.ErrorToHTTPCode(err, true), err
+	}
+
+	p := &page.Page{
+		Info: &page.Info{
+			Name:   i.Name(),
+			Path:   i.VirtualPath,
+			IsDir:  false,
+			Data:   i,
+			User:   u,
+			Config: c,
+		},
+	}
+
+	if i.CanBeEdited() && u.AllowEdit {
+		p.Data, err = i.GetEditor()
+		if err != nil {
+			return http.StatusInternalServerError, err
+		}
+
+		return p.PrintAsHTML(w, "frontmatter", "editor")
+	}
+
+	return p.PrintAsHTML(w, "single")
+}
diff --git a/preprocess.go b/preprocess.go
index 0d039252..bec8b2f7 100644
--- a/preprocess.go
+++ b/preprocess.go
@@ -16,7 +16,13 @@ import (
 )
 
 // processPUT is used to update a file that was edited
-func processPUT(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) {
+func processPUT(
+	w http.ResponseWriter,
+	r *http.Request,
+	c *config.Config,
+	u *config.User,
+	i *file.Info,
+) (int, error) {
 	var (
 		data      map[string]interface{}
 		file      []byte

From df888b604ac6e7222e5cf72d771f930e3fde1f38 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 12:00:45 +0100
Subject: [PATCH 20/28] move put to put

---
 file/download.go                 |  1 -
 filemanager.go                   |  3 +--
 preprocess.go => handlers/put.go | 44 +++++++++++++++-----------------
 3 files changed, 22 insertions(+), 26 deletions(-)
 delete mode 100644 file/download.go
 rename preprocess.go => handlers/put.go (72%)

diff --git a/file/download.go b/file/download.go
deleted file mode 100644
index b691ba57..00000000
--- a/file/download.go
+++ /dev/null
@@ -1 +0,0 @@
-package file
diff --git a/filemanager.go b/filemanager.go
index 23d31bbd..d53ae279 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -83,8 +83,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 			// Preprocess the PUT request if it's the case
 			if r.Method == http.MethodPut {
-				_, err = processPUT(w, r, c, user, fi)
-				if err != nil {
+				if handlers.PreProccessPUT(w, r, c, user, fi) != nil {
 					return http.StatusInternalServerError, err
 				}
 			}
diff --git a/preprocess.go b/handlers/put.go
similarity index 72%
rename from preprocess.go
rename to handlers/put.go
index bec8b2f7..e13a463d 100644
--- a/preprocess.go
+++ b/handlers/put.go
@@ -1,4 +1,4 @@
-package filemanager
+package handlers
 
 import (
 	"bytes"
@@ -15,19 +15,17 @@ import (
 	"github.com/spf13/hugo/parser"
 )
 
-// processPUT is used to update a file that was edited
-func processPUT(
+// PreProccessPUT is used to update a file that was edited
+func PreProccessPUT(
 	w http.ResponseWriter,
 	r *http.Request,
 	c *config.Config,
 	u *config.User,
 	i *file.Info,
-) (int, error) {
+) (err error) {
 	var (
 		data      map[string]interface{}
 		file      []byte
-		code      int
-		err       error
 		kind      string
 		rawBuffer = new(bytes.Buffer)
 	)
@@ -39,22 +37,22 @@ func processPUT(
 		err = json.Unmarshal(rawBuffer.Bytes(), &data)
 
 		if err != nil {
-			return http.StatusInternalServerError, err
+			return
 		}
 	}
 
 	switch kind {
 	case "frontmatter-only":
-		if file, code, err = parseFrontMatterOnlyFile(data, i.Name()); err != nil {
-			return http.StatusInternalServerError, err
+		if file, err = parseFrontMatterOnlyFile(data, i.Name()); err != nil {
+			return
 		}
 	case "content-only":
 		mainContent := data["content"].(string)
 		mainContent = strings.TrimSpace(mainContent)
 		file = []byte(mainContent)
 	case "complete":
-		if file, code, err = parseCompleteFile(data, i.Name(), u.FrontMatter); err != nil {
-			return http.StatusInternalServerError, err
+		if file, err = parseCompleteFile(data, i.Name(), u.FrontMatter); err != nil {
+			return
 		}
 	default:
 		file = rawBuffer.Bytes()
@@ -62,13 +60,13 @@ func processPUT(
 
 	// Overwrite the request Body
 	r.Body = ioutil.NopCloser(bytes.NewReader(file))
-	return code, nil
+	return
 }
 
 // parseFrontMatterOnlyFile parses a frontmatter only file
-func parseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, int, error) {
+func parseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, error) {
 	frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".")
-	f, code, err := parseFrontMatter(data, frontmatter)
+	f, err := parseFrontMatter(data, frontmatter)
 	fString := string(f)
 
 	// If it's toml or yaml, strip frontmatter identifier
@@ -83,11 +81,11 @@ func parseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, int, e
 	}
 
 	f = []byte(fString)
-	return f, code, err
+	return f, err
 }
 
 // parseFrontMatter is the frontmatter parser
-func parseFrontMatter(data interface{}, frontmatter string) ([]byte, int, error) {
+func parseFrontMatter(data interface{}, frontmatter string) ([]byte, error) {
 	var mark rune
 
 	switch frontmatter {
@@ -98,20 +96,20 @@ func parseFrontMatter(data interface{}, frontmatter string) ([]byte, int, error)
 	case "yaml":
 		mark = rune('-')
 	default:
-		return []byte{}, http.StatusBadRequest, errors.New("Can't define the frontmatter.")
+		return []byte{}, errors.New("Can't define the frontmatter.")
 	}
 
 	f, err := parser.InterfaceToFrontMatter(data, mark)
 
 	if err != nil {
-		return []byte{}, http.StatusInternalServerError, err
+		return []byte{}, err
 	}
 
-	return f, http.StatusOK, nil
+	return f, nil
 }
 
 // parseCompleteFile parses a complete file
-func parseCompleteFile(data map[string]interface{}, filename string, frontmatter string) ([]byte, int, error) {
+func parseCompleteFile(data map[string]interface{}, filename string, frontmatter string) ([]byte, error) {
 	mainContent := ""
 
 	if _, ok := data["content"]; ok {
@@ -127,16 +125,16 @@ func parseCompleteFile(data map[string]interface{}, filename string, frontmatter
 		data["date"] = data["date"].(string) + ":00"
 	}
 
-	front, code, err := parseFrontMatter(data, frontmatter)
+	front, err := parseFrontMatter(data, frontmatter)
 
 	if err != nil {
 		fmt.Println(frontmatter)
-		return []byte{}, code, err
+		return []byte{}, err
 	}
 
 	// Generates the final file
 	f := new(bytes.Buffer)
 	f.Write(front)
 	f.Write([]byte(mainContent))
-	return f.Bytes(), http.StatusOK, nil
+	return f.Bytes(), nil
 }

From 5fce287cd23a5318da4bae441180c54861cfea27 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 12:07:19 +0100
Subject: [PATCH 21/28] move some stuff

---
 file/info.go                            | 30 ++++++++++++++++++++++--
 frontmatter/frontmatter.go              |  6 ++---
 {file => handlers}/editor.go            | 31 +++----------------------
 handlers/listing.go                     |  4 ++--
 handlers/single.go                      |  6 ++---
 page/page.go                            |  4 ++--
 utils/{ => errors}/errors.go            |  2 +-
 utils/{ => variables}/types.go          |  2 +-
 utils/{ => variables}/variables.go      |  2 +-
 utils/{ => variables}/variables_test.go |  2 +-
 10 files changed, 45 insertions(+), 44 deletions(-)
 rename {file => handlers}/editor.go (78%)
 rename utils/{ => errors}/errors.go (95%)
 rename utils/{ => variables}/types.go (94%)
 rename utils/{ => variables}/variables.go (98%)
 rename utils/{ => variables}/variables_test.go (97%)

diff --git a/file/info.go b/file/info.go
index 9ffaa66d..18060e38 100644
--- a/file/info.go
+++ b/file/info.go
@@ -10,7 +10,7 @@ import (
 
 	humanize "github.com/dustin/go-humanize"
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/utils"
+	"github.com/hacdias/caddy-filemanager/utils/errors"
 )
 
 // Info contains the information about a particular file or directory
@@ -41,7 +41,7 @@ func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error)
 
 	i.FileInfo, err = os.Stat(i.Path)
 	if err != nil {
-		return i, utils.ErrorToHTTPCode(err, false), err
+		return i, errors.ErrorToHTTPCode(err, false), err
 	}
 
 	return i, 0, nil
@@ -74,6 +74,32 @@ func (i Info) HumanModTime(format string) string {
 	return i.ModTime().Format(format)
 }
 
+// CanBeEdited checks if the extension of a file is supported by the editor
+func (i Info) CanBeEdited() bool {
+	if i.Type == "text" {
+		return true
+	}
+
+	extensions := [...]string{
+		"md", "markdown", "mdown", "mmark",
+		"asciidoc", "adoc", "ad",
+		"rst",
+		".json", ".toml", ".yaml",
+		".css", ".sass", ".scss",
+		".js",
+		".html",
+		".txt",
+	}
+
+	for _, extension := range extensions {
+		if strings.HasSuffix(i.Name(), extension) {
+			return true
+		}
+	}
+
+	return false
+}
+
 func simplifyMediaType(name string) string {
 	if strings.HasPrefix(name, "video") {
 		return "video"
diff --git a/frontmatter/frontmatter.go b/frontmatter/frontmatter.go
index 18edbc5b..d50df10e 100644
--- a/frontmatter/frontmatter.go
+++ b/frontmatter/frontmatter.go
@@ -13,7 +13,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/BurntSushi/toml"
-	"github.com/hacdias/caddy-filemanager/utils"
+	"github.com/hacdias/caddy-filemanager/utils/variables"
 
 	"github.com/spf13/cast"
 )
@@ -126,9 +126,9 @@ func rawToPretty(config interface{}, parent *Block) *Content {
 	}
 
 	for name, element := range cnf {
-		if utils.IsMap(element) {
+		if variables.IsMap(element) {
 			objects = append(objects, handleObjects(element, parent, name))
-		} else if utils.IsSlice(element) {
+		} else if variables.IsSlice(element) {
 			arrays = append(arrays, handleArrays(element, parent, name))
 		} else {
 			if name == "title" && parent.Name == mainName {
diff --git a/file/editor.go b/handlers/editor.go
similarity index 78%
rename from file/editor.go
rename to handlers/editor.go
index e0cd3ba8..f9343538 100644
--- a/file/editor.go
+++ b/handlers/editor.go
@@ -1,10 +1,11 @@
-package file
+package handlers
 
 import (
 	"bytes"
 	"path/filepath"
 	"strings"
 
+	"github.com/hacdias/caddy-filemanager/file"
 	"github.com/hacdias/caddy-filemanager/frontmatter"
 	"github.com/spf13/hugo/parser"
 )
@@ -18,7 +19,7 @@ type Editor struct {
 }
 
 // GetEditor gets the editor based on a FileInfo struct
-func (i *Info) GetEditor() (*Editor, error) {
+func GetEditor(i *file.Info) (*Editor, error) {
 	// Create a new editor variable and set the mode
 	editor := new(Editor)
 	editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name()), ".")
@@ -81,29 +82,3 @@ func (i *Info) GetEditor() (*Editor, error) {
 
 	return editor, nil
 }
-
-// CanBeEdited checks if the extension of a file is supported by the editor
-func (i Info) CanBeEdited() bool {
-	if i.Type == "text" {
-		return true
-	}
-
-	extensions := [...]string{
-		"md", "markdown", "mdown", "mmark",
-		"asciidoc", "adoc", "ad",
-		"rst",
-		".json", ".toml", ".yaml",
-		".css", ".sass", ".scss",
-		".js",
-		".html",
-		".txt",
-	}
-
-	for _, extension := range extensions {
-		if strings.HasSuffix(i.Name(), extension) {
-			return true
-		}
-	}
-
-	return false
-}
diff --git a/handlers/listing.go b/handlers/listing.go
index 13b36d33..28d022db 100644
--- a/handlers/listing.go
+++ b/handlers/listing.go
@@ -9,7 +9,7 @@ import (
 	"github.com/hacdias/caddy-filemanager/config"
 	"github.com/hacdias/caddy-filemanager/file"
 	"github.com/hacdias/caddy-filemanager/page"
-	"github.com/hacdias/caddy-filemanager/utils"
+	"github.com/hacdias/caddy-filemanager/utils/errors"
 	"github.com/mholt/caddy/caddyhttp/httpserver"
 )
 
@@ -20,7 +20,7 @@ func ServeListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *c
 	// Loads the content of the directory
 	listing, err := file.GetListing(u, i.VirtualPath, r.URL.Path)
 	if err != nil {
-		return utils.ErrorToHTTPCode(err, true), err
+		return errors.ErrorToHTTPCode(err, true), err
 	}
 
 	listing.Context = httpserver.Context{
diff --git a/handlers/single.go b/handlers/single.go
index 93dd3b85..2c22f885 100644
--- a/handlers/single.go
+++ b/handlers/single.go
@@ -6,7 +6,7 @@ import (
 	"github.com/hacdias/caddy-filemanager/config"
 	"github.com/hacdias/caddy-filemanager/file"
 	"github.com/hacdias/caddy-filemanager/page"
-	"github.com/hacdias/caddy-filemanager/utils"
+	"github.com/hacdias/caddy-filemanager/utils/errors"
 )
 
 // ServeSingle serves a single file in an editor (if it is editable), shows the
@@ -14,7 +14,7 @@ import (
 func ServeSingle(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) {
 	err := i.Read()
 	if err != nil {
-		return utils.ErrorToHTTPCode(err, true), err
+		return errors.ErrorToHTTPCode(err, true), err
 	}
 
 	p := &page.Page{
@@ -29,7 +29,7 @@ func ServeSingle(w http.ResponseWriter, r *http.Request, c *config.Config, u *co
 	}
 
 	if i.CanBeEdited() && u.AllowEdit {
-		p.Data, err = i.GetEditor()
+		p.Data, err = GetEditor(i)
 		if err != nil {
 			return http.StatusInternalServerError, err
 		}
diff --git a/page/page.go b/page/page.go
index 2b2ca46e..dd7c60a3 100644
--- a/page/page.go
+++ b/page/page.go
@@ -11,7 +11,7 @@ import (
 
 	"github.com/hacdias/caddy-filemanager/assets"
 	"github.com/hacdias/caddy-filemanager/config"
-	"github.com/hacdias/caddy-filemanager/utils"
+	"github.com/hacdias/caddy-filemanager/utils/variables"
 )
 
 // Page contains the informations and functions needed to show the Page
@@ -81,7 +81,7 @@ func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
 	// Create the functions map, then the template, check for erros and
 	// execute the template if there aren't errors
 	functions := template.FuncMap{
-		"Defined": utils.Defined,
+		"Defined": variables.Defined,
 		"CSS": func(s string) template.CSS {
 			return template.CSS(s)
 		},
diff --git a/utils/errors.go b/utils/errors/errors.go
similarity index 95%
rename from utils/errors.go
rename to utils/errors/errors.go
index 35841d85..ad4a2743 100644
--- a/utils/errors.go
+++ b/utils/errors/errors.go
@@ -1,4 +1,4 @@
-package utils
+package errors
 
 import (
 	"net/http"
diff --git a/utils/types.go b/utils/variables/types.go
similarity index 94%
rename from utils/types.go
rename to utils/variables/types.go
index 7e6b408b..ee43dad3 100644
--- a/utils/types.go
+++ b/utils/variables/types.go
@@ -1,4 +1,4 @@
-package utils
+package variables
 
 import "reflect"
 
diff --git a/utils/variables.go b/utils/variables/variables.go
similarity index 98%
rename from utils/variables.go
rename to utils/variables/variables.go
index 28f8383a..7a0168b4 100644
--- a/utils/variables.go
+++ b/utils/variables/variables.go
@@ -1,4 +1,4 @@
-package utils
+package variables
 
 import (
 	"errors"
diff --git a/utils/variables_test.go b/utils/variables/variables_test.go
similarity index 97%
rename from utils/variables_test.go
rename to utils/variables/variables_test.go
index 7122478d..ec76d459 100644
--- a/utils/variables_test.go
+++ b/utils/variables/variables_test.go
@@ -1,4 +1,4 @@
-package utils
+package variables
 
 import "testing"
 

From 17eff65b0dd49b4c74004bfb659f5d078d8af0af Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 12:11:08 +0100
Subject: [PATCH 22/28] move assets

---
 {assets/embed => _embed}/public/css/.jsbeautifyrc   | 0
 {assets/embed => _embed}/public/css/styles.css      | 0
 {assets/embed => _embed}/public/js/.jsbeautifyrc    | 0
 {assets/embed => _embed}/public/js/application.js   | 0
 {assets/embed => _embed}/public/js/form2js.js       | 0
 {assets/embed => _embed}/templates/.jsbeautifyrc    | 0
 {assets/embed => _embed}/templates/actions.tmpl     | 0
 {assets/embed => _embed}/templates/base.tmpl        | 0
 {assets/embed => _embed}/templates/editor.tmpl      | 0
 {assets/embed => _embed}/templates/frontmatter.tmpl | 0
 {assets/embed => _embed}/templates/listing.tmpl     | 0
 {assets/embed => _embed}/templates/minimal.tmpl     | 0
 {assets/embed => _embed}/templates/single.tmpl      | 0
 filemanager.go                                      | 2 +-
 14 files changed, 1 insertion(+), 1 deletion(-)
 rename {assets/embed => _embed}/public/css/.jsbeautifyrc (100%)
 rename {assets/embed => _embed}/public/css/styles.css (100%)
 rename {assets/embed => _embed}/public/js/.jsbeautifyrc (100%)
 rename {assets/embed => _embed}/public/js/application.js (100%)
 rename {assets/embed => _embed}/public/js/form2js.js (100%)
 rename {assets/embed => _embed}/templates/.jsbeautifyrc (100%)
 rename {assets/embed => _embed}/templates/actions.tmpl (100%)
 rename {assets/embed => _embed}/templates/base.tmpl (100%)
 rename {assets/embed => _embed}/templates/editor.tmpl (100%)
 rename {assets/embed => _embed}/templates/frontmatter.tmpl (100%)
 rename {assets/embed => _embed}/templates/listing.tmpl (100%)
 rename {assets/embed => _embed}/templates/minimal.tmpl (100%)
 rename {assets/embed => _embed}/templates/single.tmpl (100%)

diff --git a/assets/embed/public/css/.jsbeautifyrc b/_embed/public/css/.jsbeautifyrc
similarity index 100%
rename from assets/embed/public/css/.jsbeautifyrc
rename to _embed/public/css/.jsbeautifyrc
diff --git a/assets/embed/public/css/styles.css b/_embed/public/css/styles.css
similarity index 100%
rename from assets/embed/public/css/styles.css
rename to _embed/public/css/styles.css
diff --git a/assets/embed/public/js/.jsbeautifyrc b/_embed/public/js/.jsbeautifyrc
similarity index 100%
rename from assets/embed/public/js/.jsbeautifyrc
rename to _embed/public/js/.jsbeautifyrc
diff --git a/assets/embed/public/js/application.js b/_embed/public/js/application.js
similarity index 100%
rename from assets/embed/public/js/application.js
rename to _embed/public/js/application.js
diff --git a/assets/embed/public/js/form2js.js b/_embed/public/js/form2js.js
similarity index 100%
rename from assets/embed/public/js/form2js.js
rename to _embed/public/js/form2js.js
diff --git a/assets/embed/templates/.jsbeautifyrc b/_embed/templates/.jsbeautifyrc
similarity index 100%
rename from assets/embed/templates/.jsbeautifyrc
rename to _embed/templates/.jsbeautifyrc
diff --git a/assets/embed/templates/actions.tmpl b/_embed/templates/actions.tmpl
similarity index 100%
rename from assets/embed/templates/actions.tmpl
rename to _embed/templates/actions.tmpl
diff --git a/assets/embed/templates/base.tmpl b/_embed/templates/base.tmpl
similarity index 100%
rename from assets/embed/templates/base.tmpl
rename to _embed/templates/base.tmpl
diff --git a/assets/embed/templates/editor.tmpl b/_embed/templates/editor.tmpl
similarity index 100%
rename from assets/embed/templates/editor.tmpl
rename to _embed/templates/editor.tmpl
diff --git a/assets/embed/templates/frontmatter.tmpl b/_embed/templates/frontmatter.tmpl
similarity index 100%
rename from assets/embed/templates/frontmatter.tmpl
rename to _embed/templates/frontmatter.tmpl
diff --git a/assets/embed/templates/listing.tmpl b/_embed/templates/listing.tmpl
similarity index 100%
rename from assets/embed/templates/listing.tmpl
rename to _embed/templates/listing.tmpl
diff --git a/assets/embed/templates/minimal.tmpl b/_embed/templates/minimal.tmpl
similarity index 100%
rename from assets/embed/templates/minimal.tmpl
rename to _embed/templates/minimal.tmpl
diff --git a/assets/embed/templates/single.tmpl b/_embed/templates/single.tmpl
similarity index 100%
rename from assets/embed/templates/single.tmpl
rename to _embed/templates/single.tmpl
diff --git a/filemanager.go b/filemanager.go
index d53ae279..0e0d2ebb 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -1,6 +1,6 @@
 //go:generate go get github.com/jteeuwen/go-bindata
 //go:generate go install github.com/jteeuwen/go-bindata/go-bindata
-//go:generate go-bindata -pkg assets -ignore .jsbeautifyrc -prefix "assets/embed" -o assets/binary.go assets/embed/...
+//go:generate go-bindata -pkg assets -ignore .jsbeautifyrc -prefix "_embed" -o assets/binary.go _embed/...
 
 // Package filemanager provides middleware for managing files in a directory
 // when directory path is requested instead of a specific file. Based on browse

From 3430488b583a20f2fbf5147295680c81d813d70c Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 13:09:38 +0100
Subject: [PATCH 23/28] Add more info to file

---
 file/info.go | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/file/info.go b/file/info.go
index 18060e38..7fc717c3 100644
--- a/file/info.go
+++ b/file/info.go
@@ -16,6 +16,7 @@ import (
 // Info contains the information about a particular file or directory
 type Info struct {
 	os.FileInfo
+	File        *os.File
 	URL         string
 	Path        string // Relative path to Caddyfile
 	VirtualPath string // Relative path to u.FileSystem
@@ -39,11 +40,24 @@ func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error)
 	i.Path = strings.Replace(i.Path, "\\", "/", -1)
 	i.Path = filepath.Clean(i.Path)
 
-	i.FileInfo, err = os.Stat(i.Path)
+	i.File, err = os.Open(i.Path)
 	if err != nil {
 		return i, errors.ErrorToHTTPCode(err, false), err
 	}
 
+	i.FileInfo, err = i.File.Stat()
+	if err != nil {
+		return i, errors.ErrorToHTTPCode(err, true), err
+	}
+
+	p := make([]byte, 512)
+	_, err = i.File.Read(p)
+	if err != nil {
+		return i, errors.ErrorToHTTPCode(err, false), err
+	}
+
+	i.Mimetype = http.DetectContentType(p)
+	i.Type = simplifyMediaType(i.Mimetype)
 	return i, 0, nil
 }
 
@@ -53,6 +67,7 @@ func (i *Info) Read() error {
 	if err != nil {
 		return err
 	}
+
 	i.Mimetype = http.DetectContentType(i.Content)
 	i.Type = simplifyMediaType(i.Mimetype)
 	return nil

From d96bbff55082ce4f6c94b11ca0a990d4e43efb89 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 13:46:10 +0100
Subject: [PATCH 24/28] support for video and improvements

---
 _embed/public/css/styles.css |  4 ++++
 _embed/templates/single.tmpl |  6 +++++-
 file/info.go                 | 32 ++++++++++++++++++--------------
 handlers/single.go           | 11 +++++++++--
 4 files changed, 36 insertions(+), 17 deletions(-)

diff --git a/_embed/public/css/styles.css b/_embed/public/css/styles.css
index 644d9cec..0db84589 100644
--- a/_embed/public/css/styles.css
+++ b/_embed/public/css/styles.css
@@ -33,6 +33,10 @@ video {
     display: inline-block
 }
 
+video {
+    max-width: 100%;
+}
+
 audio:not([controls]) {
     display: none;
     height: 0
diff --git a/_embed/templates/single.tmpl b/_embed/templates/single.tmpl
index f360663e..e070dcd8 100644
--- a/_embed/templates/single.tmpl
+++ b/_embed/templates/single.tmpl
@@ -6,7 +6,11 @@
    {{ else if eq .Type "audio" }}
    <audio src="{{ .URL }}?raw=true"></audio>
    {{ else if eq .Type "video" }}
-   <!-- TODO: SHOW VIDEO ? -->
+   <video src="{{ .URL }}?raw=true" controls>
+      Sorry, your browser doesn't support embedded videos,
+      but don't worry, you can <a href="?download=true">download it</a>
+      and watch it with your favorite video player!
+    </video>
    {{ else if eq .Type "blob" }}
    <a href="?download=true">Download</a>
    {{ else}}
diff --git a/file/info.go b/file/info.go
index 7fc717c3..df0b40ea 100644
--- a/file/info.go
+++ b/file/info.go
@@ -16,7 +16,6 @@ import (
 // Info contains the information about a particular file or directory
 type Info struct {
 	os.FileInfo
-	File        *os.File
 	URL         string
 	Path        string // Relative path to Caddyfile
 	VirtualPath string // Relative path to u.FileSystem
@@ -40,36 +39,41 @@ func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error)
 	i.Path = strings.Replace(i.Path, "\\", "/", -1)
 	i.Path = filepath.Clean(i.Path)
 
-	i.File, err = os.Open(i.Path)
-	if err != nil {
-		return i, errors.ErrorToHTTPCode(err, false), err
-	}
-
-	i.FileInfo, err = i.File.Stat()
+	i.FileInfo, err = os.Stat(i.Path)
 	if err != nil {
 		return i, errors.ErrorToHTTPCode(err, true), err
 	}
 
-	p := make([]byte, 512)
-	_, err = i.File.Read(p)
+	return i, 0, nil
+}
+
+// RetrieveFileType obtains the mimetype and a simplified internal Type
+// using the first 512 bytes from the file.
+func (i *Info) RetrieveFileType() error {
+	file, err := os.Open(i.Path)
 	if err != nil {
-		return i, errors.ErrorToHTTPCode(err, false), err
+		return err
+	}
+	defer file.Close()
+
+	p := make([]byte, 512)
+	_, err = file.Read(p)
+	if err != nil {
+		return err
 	}
 
 	i.Mimetype = http.DetectContentType(p)
 	i.Type = simplifyMediaType(i.Mimetype)
-	return i, 0, nil
+	return nil
 }
 
+// Reads the file.
 func (i *Info) Read() error {
 	var err error
 	i.Content, err = ioutil.ReadFile(i.Path)
 	if err != nil {
 		return err
 	}
-
-	i.Mimetype = http.DetectContentType(i.Content)
-	i.Type = simplifyMediaType(i.Mimetype)
 	return nil
 }
 
diff --git a/handlers/single.go b/handlers/single.go
index 2c22f885..85d0ff5a 100644
--- a/handlers/single.go
+++ b/handlers/single.go
@@ -12,11 +12,18 @@ import (
 // ServeSingle serves a single file in an editor (if it is editable), shows the
 // plain file, or downloads it if it can't be shown.
 func ServeSingle(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) {
-	err := i.Read()
-	if err != nil {
+	var err error
+
+	if err = i.RetrieveFileType(); err != nil {
 		return errors.ErrorToHTTPCode(err, true), err
 	}
 
+	if i.Type == "text" {
+		if err = i.Read(); err != nil {
+			return errors.ErrorToHTTPCode(err, true), err
+		}
+	}
+
 	p := &page.Page{
 		Info: &page.Info{
 			Name:   i.Name(),

From 4e7c730faf40f3ee45c0f2d9baaa506df7a6d863 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 15:45:45 +0100
Subject: [PATCH 25/28] download improvements; CSRF token commented

---
 filemanager.go       | 47 ++++++++++++++++++++++++++++++++++++++------
 handlers/download.go | 21 ++++++++++++++++----
 2 files changed, 58 insertions(+), 10 deletions(-)

diff --git a/filemanager.go b/filemanager.go
index 0e0d2ebb..8827e772 100644
--- a/filemanager.go
+++ b/filemanager.go
@@ -43,6 +43,10 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 			return f.Next.ServeHTTP(w, r)
 		}
 
+		w.Header().Set("x-frame-options", "SAMEORIGIN")
+		w.Header().Set("x-content-type", "nosniff")
+		w.Header().Set("x-xss-protection", "1; mode=block")
+
 		c = &f.Configs[i]
 
 		// Checks if the URL matches the Assets URL. Returns the asset if the
@@ -65,6 +69,10 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 
 		// Checks if the request URL is for the WebDav server
 		if strings.HasPrefix(r.URL.Path, c.WebDavURL) {
+			//	if !c.CheckToken(r) {
+			//	return http.StatusForbidden, nil
+			//	}
+
 			// Checks for user permissions relatively to this PATH
 			if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.WebDavURL)) {
 				return http.StatusForbidden, nil
@@ -105,6 +113,36 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 		}
 
 		if r.Method == http.MethodGet {
+			// Generate anti security token.
+			/* c.GenerateToken()
+
+			http.SetCookie(w, &http.Cookie{
+				Name:     "token",
+				Value:    c.Token,
+				Path:     "/",
+				HttpOnly: true,
+			})
+
+			co, err := r.Cookie("token")
+			fmt.Println(co.Value) */
+
+			/* Name  string
+			   Value string
+
+			   Path       string    // optional
+			   Domain     string    // optional
+			   Expires    time.Time // optional
+			   RawExpires string    // for reading cookies only
+
+			   // MaxAge=0 means no 'Max-Age' attribute specified.
+			   // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
+			   // MaxAge>0 means Max-Age attribute present and given in seconds
+			   MaxAge   int
+			   Secure   bool
+			   HttpOnly bool
+			   Raw      string
+			   Unparsed []string // Raw text of unparsed attribute-value pairs*/
+
 			// Gets the information of the directory/file
 			fi, code, err = file.GetInfo(r.URL, c, user)
 			if err != nil {
@@ -121,9 +159,6 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 				return 0, nil
 			}
 
-			// Generate anti security token.
-			c.GenerateToken()
-
 			switch {
 			case r.URL.Query().Get("download") != "":
 				code, err = handlers.Download(w, r, c, fi)
@@ -146,9 +181,9 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
 		if r.Method == http.MethodPost {
 			// TODO: This anti CSCF measure is not being applied to requests
 			// to the WebDav URL namespace. Anyone has ideas?
-			if !c.CheckToken(r) {
-				return http.StatusForbidden, nil
-			}
+			//	if !c.CheckToken(r) {
+			//	return http.StatusForbidden, nil
+			//	}
 
 			// VCS commands.
 			if r.Header.Get("Command") != "" {
diff --git a/handlers/download.go b/handlers/download.go
index f4fc95a2..4b064499 100644
--- a/handlers/download.go
+++ b/handlers/download.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/hacdias/caddy-filemanager/config"
 	"github.com/hacdias/caddy-filemanager/file"
@@ -23,6 +24,18 @@ func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.
 		return 0, nil
 	}
 
+	files := []string{}
+	names := strings.Split(r.URL.Query().Get("files"), ",")
+
+	if len(names) != 0 {
+		for _, name := range names {
+			files = append(files, filepath.Join(i.Path, name))
+		}
+
+	} else {
+		files = append(files, i.Path)
+	}
+
 	if query == "true" {
 		query = "zip"
 	}
@@ -44,13 +57,13 @@ func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.
 
 	switch query {
 	case "zip":
-		extension, err = ".zip", archiver.Zip.Make(tempfile, []string{i.Path})
+		extension, err = ".zip", archiver.Zip.Make(tempfile, files)
 	case "tar":
-		extension, err = ".tar", archiver.Tar.Make(tempfile, []string{i.Path})
+		extension, err = ".tar", archiver.Tar.Make(tempfile, files)
 	case "targz":
-		extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, []string{i.Path})
+		extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, files)
 	case "tarbz2":
-		extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, []string{i.Path})
+		extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, files)
 	default:
 		return http.StatusNotImplemented, nil
 	}

From 82cc6e77bdd7ad75e8f5195946b159ae35107e07 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Sat, 22 Oct 2016 16:37:20 +0100
Subject: [PATCH 26/28] Improve Mimetype detecting

---
 file/info.go | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/file/info.go b/file/info.go
index df0b40ea..bec3901e 100644
--- a/file/info.go
+++ b/file/info.go
@@ -2,6 +2,7 @@ package file
 
 import (
 	"io/ioutil"
+	"mime"
 	"net/http"
 	"net/url"
 	"os"
@@ -50,25 +51,27 @@ func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error)
 // RetrieveFileType obtains the mimetype and a simplified internal Type
 // using the first 512 bytes from the file.
 func (i *Info) RetrieveFileType() error {
-	file, err := os.Open(i.Path)
-	if err != nil {
-		return err
-	}
-	defer file.Close()
+	i.Mimetype = mime.TypeByExtension(filepath.Ext(i.Name()))
 
-	p := make([]byte, 512)
-	_, err = file.Read(p)
-	if err != nil {
-		return err
+	if i.Mimetype == "" {
+		err := i.Read()
+		if err != nil {
+			return err
+		}
+
+		i.Mimetype = http.DetectContentType(i.Content)
 	}
 
-	i.Mimetype = http.DetectContentType(p)
 	i.Type = simplifyMediaType(i.Mimetype)
 	return nil
 }
 
 // Reads the file.
 func (i *Info) Read() error {
+	if len(i.Content) != 0 {
+		return nil
+	}
+
 	var err error
 	i.Content, err = ioutil.ReadFile(i.Path)
 	if err != nil {

From cb72d8c6c2999c981c95d4fe3c19d3338e4cf580 Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 25 Oct 2016 21:08:26 +0100
Subject: [PATCH 27/28] close #29

---
 _embed/public/css/styles.css    |  35 ++---
 _embed/public/js/application.js |  37 +++---
 _embed/templates/actions.tmpl   |  42 +++---
 _embed/templates/base.tmpl      | 229 +++++++++++++++-----------------
 4 files changed, 173 insertions(+), 170 deletions(-)

diff --git a/_embed/public/css/styles.css b/_embed/public/css/styles.css
index 0db84589..3368741b 100644
--- a/_embed/public/css/styles.css
+++ b/_embed/public/css/styles.css
@@ -264,7 +264,7 @@ textarea {
 body {
     font-family: 'Roboto', sans-serif;
     padding-top: 5em;
-    background-color: #fcfcfc;
+    background-color: #ffffff;
     text-rendering: optimizespeed;
 }
 
@@ -473,6 +473,8 @@ header {
     z-index: 999;
     padding: 1.7em 0;
     background-color: #2196f3;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.075);
+    box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
 }
 
 header h1 {
@@ -481,7 +483,9 @@ header h1 {
 }
 
 header a,
-header a:hover {
+header a:hover,
+#toolbar a,
+#toolbar a:hover {
     color: inherit;
 }
 
@@ -680,8 +684,7 @@ header .only-side {
     display: none;
 }
 
-header #prev:hover+.prev-links,
-header .prev-links:hover {
+.action:hover ul {
     display: flex;
 }
 
@@ -689,9 +692,9 @@ header .prev-links:hover {
     border-radius: 0;
 }
 
-header .prev-links {
+.action ul {
     position: absolute;
-    top: 4em;
+    top: 3.1em;
     left: 0;
     color: #7d7d7d;
     list-style: none;
@@ -703,37 +706,39 @@ header .prev-links {
     flex-direction: column-reverse;
     display: none;
     transition: .2s ease all;
-    min-width: 12em;
+    min-width: 3em;
+    z-index: 99999;
 }
 
-header .prev-links:before {
+.action ul:before {
     top: -16px;
     left: 1em;
     right: auto;
     border: 8px solid transparent;
-    border-bottom-color: #68efad;
+    border-bottom-color: #ffffff;
     content: '';
     position: absolute;
 }
 
-header .prev-links a {
-    padding: .5em;
+.action ul a {
+    padding: .3em .5em;
     border-bottom: 1px solid #f5f5f5;
     transition: .2s ease all;
+    text-align: left;
 }
 
-header .prev-links a:first-child {
+.action ul a:first-child {
     border: 0;
     border-bottom-right-radius: .2em;
     border-bottom-left-radius: .2em;
 }
 
-header .prev-links a:last-child {
+.action ul a:last-child {
     border-top-right-radius: .2em;
     border-top-left-radius: .2em;
 }
 
-header .prev-links a:hover {
+.action ul a:hover {
     background-color: #f5f5f5;
 }
 
@@ -1181,4 +1186,4 @@ i.spin {
         column-count: 1;
         column-gap: 0;
     }
-}
+}
\ No newline at end of file
diff --git a/_embed/public/js/application.js b/_embed/public/js/application.js
index 12c73c70..4ba2ed56 100644
--- a/_embed/public/js/application.js
+++ b/_embed/public/js/application.js
@@ -151,22 +151,6 @@ var preventDefault = function(event) {
     event.preventDefault();
 }
 
-// Download file event
-var downloadEvent = function(event) {
-    if (this.classList.contains('disabled')) {
-        return false;
-    }
-    if (selectedItems.length) {
-        Array.from(selectedItems).forEach(item => {
-            window.open(item + "?download=true");
-        });
-        return false;
-    }
-
-    window.open(window.location + "?download=true");
-    return false;
-}
-
 // Remove the last directory of an url
 var RemoveLastDirectoryPartOf = function(url) {
     var arr = url.split('/');
@@ -466,6 +450,8 @@ document.addEventListener("changed-selected", function(event) {
             document.getElementById("rename").classList.remove("disabled");
         }
 
+        redefineDownloadURLs();
+
         return false;
     }
 
@@ -473,6 +459,22 @@ document.addEventListener("changed-selected", function(event) {
     return false;
 });
 
+var redefineDownloadURLs = function() {
+    let files = "";
+
+    for (let i = 0; i < selectedItems.length; i++) {
+        files += selectedItems[i].replace(window.location.pathname, "") + ",";
+    }
+
+    files = files.substring(0, files.length - 1);
+    files = encodeURIComponent(files);
+
+    let links = document.querySelectorAll("#download ul a");
+    Array.from(links).forEach(link => {
+        link.href = "?download=" + link.dataset.format + "&files=" + files;
+    });
+}
+
 var searchEvent = function(event) {
     let value = this.value;
     let box = document.querySelector('#search div');
@@ -891,7 +893,6 @@ document.addEventListener("DOMContentLoaded", function(event) {
         document.getElementById("delete").addEventListener("click", deleteEvent);
     }
 
-    document.getElementById("download").addEventListener("click", downloadEvent);
     document.getElementById("open-nav").addEventListener("click", event => {
         document.querySelector("header > div:nth-child(2)").classList.toggle("active");
     });
@@ -908,4 +909,4 @@ document.addEventListener("DOMContentLoaded", function(event) {
     }
 
     return false;
-});
\ No newline at end of file
+});
diff --git a/_embed/templates/actions.tmpl b/_embed/templates/actions.tmpl
index 069598f2..a42d11d1 100644
--- a/_embed/templates/actions.tmpl
+++ b/_embed/templates/actions.tmpl
@@ -1,18 +1,28 @@
 {{ define "actions" }}
- <div class="action" id="open">
-  <i class="material-icons" title="See raw">open_in_new</i> <span>See raw</span>
- </div>
- {{ if and .IsDir .User.AllowEdit }}
-  <div class="action" id="rename">
-   <i class="material-icons" title="Edit">mode_edit</i>
-  </div>
- {{ end }}
- <div class="action" id="download">
-  <i class="material-icons" title="Download">file_download</i> <span>Download</span>
- </div>
- {{ if .User.AllowEdit }}
- <div class="action" id="delete">
-  <i class="material-icons" title="Delete">delete</i> <span>Delete</span>
- </div>
- {{ end }}
+<div class="action" id="open">
+    <i class="material-icons" title="See raw">open_in_new</i> <span>See raw</span>
+</div>
+{{ if and .IsDir .User.AllowEdit }}
+<div class="action" id="rename">
+    <i class="material-icons" title="Edit">mode_edit</i>
+</div>
+{{ end }}
+<div class="action" id="download">
+    <a href="?download=true">
+        <i class="material-icons" title="Download">file_download</i> <span>Download</span>
+    </a>
+    {{ if .IsDir }}
+    <ul class="prev-links">
+        <a data-format="tarbz2" href="?download=tarbz2"><li>tar.bz2</li></a>
+        <a data-format="targz" href="?download=targz"><li>tar.gz</li></a>
+        <a data-format="tar" href="?download=tar"><li>tar</li></a>
+        <a data-format="zip" href="?download=zip"><li>zip</li></a>
+    </ul>
+    {{ end }}
+</div>
+{{ if .User.AllowEdit }}
+<div class="action" id="delete">
+    <i class="material-icons" title="Delete">delete</i> <span>Delete</span>
+</div>
+{{ end }}
 {{ end }}
diff --git a/_embed/templates/base.tmpl b/_embed/templates/base.tmpl
index 86f4e844..679795e7 100644
--- a/_embed/templates/base.tmpl
+++ b/_embed/templates/base.tmpl
@@ -1,139 +1,126 @@
-{{ $absURL := .Config.AbsoluteURL }}
-
 <!DOCTYPE html>
 <html>
- <head>
-  <title>{{.Name}}</title>
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
-  <link href='https://fonts.googleapis.com/css?family=Roboto:400,500' rel='stylesheet' type='text/css'>
-  <link rel="stylesheet" href="{{ .Config.AbsoluteURL }}/_filemanagerinternal/css/styles.css">
-
-  {{ if ne .User.StyleSheet "" }}
-   <style>
-    {{ CSS .User.StyleSheet }}
-   </style>
-  {{ end }}
- </head>
- <body>
-  <header>
-   <div>
-    {{ $lnk := .PreviousLink }}
-
-     <div class="action{{ if eq $lnk ""}} disabled{{ end }}" id="prev">
-         {{ if ne $lnk ""}}
-          <a href="{{ $lnk }}">
-          {{ end }}
-      <i class="material-icons" title="Previous">subdirectory_arrow_left</i>
-      {{ if ne $lnk ""}}
-      </a>
-      {{ end }}
-
-    </div>
-
-     {{ if ne $lnk ""}}
-     <ul class="prev-links">
-     {{ range $link, $name := .BreadcrumbMap }}
-        <a href="{{ $absURL }}{{ $link }}"><li>{{ $name }}</li></a>
-     {{ end }}
-     </ul>
-     {{ end }}
-
-    <div class="action" id="open-nav">
-        <i class="material-icons" title="Menu">menu</i>
-    </div>
-
-    <p>
-     {{ if ne .Name "/"}}
-      {{ .Name }}
-     </p>
+{{ $absURL := .Config.AbsoluteURL }}
+<head>
+    <title>{{.Name}}</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+    <link href='https://fonts.googleapis.com/css?family=Roboto:400,500' rel='stylesheet' type='text/css'>
+    <link rel="stylesheet" href="{{ .Config.AbsoluteURL }}/_filemanagerinternal/css/styles.css">
+    {{ if ne .User.StyleSheet "" }}
+    <style>{{ CSS .User.StyleSheet }}</style>
     {{ end }}
-   </div>
-
-   <div>
-       <div class="only-side">
-           {{ $lnk := .PreviousLink }}
-           {{ if ne $lnk ""}}
-            <a href="{{ $lnk }}">
-            {{ end }}
+</head>
+<body>
+    <header>
+        <div>
+            {{ $lnk := .PreviousLink }}
             <div class="action{{ if eq $lnk ""}} disabled{{ end }}" id="prev">
-             <i class="material-icons" title="Previous">subdirectory_arrow_left</i>
+                {{ if ne $lnk ""}}<a href="{{ $lnk }}">{{ end }}
+                    <i class="material-icons" title="Previous">subdirectory_arrow_left</i>
+                {{ if ne $lnk ""}}</a>{{ end }}
+
+                {{ if ne $lnk ""}}
+                    <ul class="prev-links">
+                    {{ range $link, $name := .BreadcrumbMap }}<a href="{{ $absURL }}{{ $link }}"><li>{{ $name }}</li></a>{{ end }}
+                    </ul>
+                {{ end }}
             </div>
 
-            {{ if ne $lnk ""}}
-            </a>
-           {{ end }}
+            <div class="action" id="open-nav">
+                <i class="material-icons" title="Menu">menu</i>
+            </div>
 
-           <p>
-            <a href="{{ if eq .Config.AbsoluteURL "" }}/{{ else }}{{ .Config.AbsoluteURL }}{{ end }}">
-             File Manager
-            </a>
-            </p>
-       </div>
-
-       {{ if .IsDir}}
-
-       {{ if .User.AllowCommands }}
-       <div id="search">
-           <i class="material-icons" title="Storage">storage</i>
-           <input type="text" placeholder="Execute a command...">
-           <div>Write your git, mercurial or svn command and press enter.</div>
-       </div>
-       {{ end }}
-
-        <div class="action" id="view">
-         <i class="material-icons" title="Switch view">view_headline</i> <span>Switch view</span>
+            {{ if ne .Name "/"}}<p>{{ .Name }}</p>{{ end }}
         </div>
-        {{ if .User.AllowNew }}
-        <div class="action" id="upload">
-         <i class="material-icons" title="Upload">file_upload</i> <span>Upload</span>
+
+        <div>
+            <div class="only-side">
+                {{ $lnk := .PreviousLink }}
+                {{ if ne $lnk ""}}<a href="{{ $lnk }}">{{ end }}
+                    <div class="action{{ if eq $lnk ""}} disabled{{ end }}" id="prev">
+                        <i class="material-icons" title="Previous">subdirectory_arrow_left</i>
+                    </div>
+                {{ if ne $lnk ""}}</a>{{ end }}
+
+                <p><a href="{{ if eq .Config.AbsoluteURL "" }}/{{ else }}{{ .Config.AbsoluteURL }}{{ end }}">File Manager</a></p>
+            </div>
+
+            {{ if .IsDir}}
+                {{ if .User.AllowCommands }}
+                <div id="search">
+                    <i class="material-icons" title="Storage">storage</i>
+                    <input type="text" placeholder="Execute a command...">
+                    <div>Write your git, mercurial or svn command and press enter.</div>
+                </div>
+                {{ end }}
+
+                <div class="action" id="view">
+                    <i class="material-icons" title="Switch view">view_headline</i> <span>Switch view</span>
+                </div>
+
+                {{ if .User.AllowNew }}
+                <div class="action" id="upload">
+                    <i class="material-icons" title="Upload">file_upload</i> <span>Upload</span>
+                </div>
+                {{ end }}
+
+                <div class="action">
+                    <a href="?download=true">
+                        <i class="material-icons" title="Download">file_download</i> <span>Download</span>
+                    </a>
+                    <ul class="prev-links">
+                        <a href="?download=tarbz2"><li>tar.bz2</li></a>
+                        <a href="?download=targz"><li>tar.gz</li></a>
+                        <a href="?download=tar"><li>tar</li></a>
+                        <a href="?download=zip"><li>zip</li></a>
+                    </ul>
+                </div>
+            {{ else }}
+            {{ template "actions" . }}
+            {{ end }}
+
+            <div class="action" id="logout">
+                <i class="material-icons" title="Logout">exit_to_app</i> <span>Logout</span>
+            </div>
         </div>
-        {{ end }}
-        {{ else }}
-        {{ template "actions" . }}
-       {{ end }}
+        <div id="overlay"></div>
+    </header>
 
-       <div class="action" id="logout">
-        <i class="material-icons" title="Logout">exit_to_app</i> <span>Logout</span>
-       </div>
-   </div>
-   <div id="overlay"></div>
-  </header>
-
-  {{ if .IsDir }}
-   <div id="toolbar">
+    {{ if .IsDir }}
+    <div id="toolbar">
     <div>
-     <div class="action" id="back">
-      <i class="material-icons" title="Back">arrow_back</i>
-     </div>
-     <p>
-      <span id="selected-number">0</span>
-      selected.</p>
+    <div class="action" id="back">
+    <i class="material-icons" title="Back">arrow_back</i>
+    </div>
+    <p>
+    <span id="selected-number">0</span>
+    selected.</p>
     </div>
     <div>
-     {{ template "actions" . }}
+    {{ template "actions" . }}
     </div>
-   </div>
-  {{ end }}
+    </div>
+    {{ end }}
 
-  <main>
-   {{ template "content" . }}
-   <span id="token">{{ .Config.Token }}</span>
-  </main>
+    <main>
+    {{ template "content" . }}
+    <span id="token">{{ .Config.Token }}</span>
+    </main>
 
-  <footer>
-   Served with
-   <a rel="noopener noreferrer" href="https://caddyserver.com">Caddy</a>
-   and
-   <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.
-  </footer>
+    <footer>
+    Served with
+    <a rel="noopener noreferrer" href="https://caddyserver.com">Caddy</a>
+    and
+    <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.
+    </footer>
 
-  <!-- SCRIPTS -->
-  <!-- User Data and Permissions; WebDavURL -->
-  <script>var user = JSON.parse('{{ Marshal .User }}'), webdavURL = "{{.Config.WebDavURL}}", baseURL = "{{.Config.BaseURL}}";</script>
-  <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js"></script>
-  <script src="{{ .Config.AbsoluteURL }}/_filemanagerinternal/js/form2js.js"></script>
-  <script src="{{ .Config.AbsoluteURL }}/_filemanagerinternal/js/application.js"></script>
-  {{ if .Config.HugoEnabled }}<script src="{{ .Config.AbsoluteURL }}/_hugointernal/js/application.js"></script>{{ end }}
- </body>
+    <!-- SCRIPTS -->
+    <!-- User Data and Permissions; WebDavURL -->
+    <script>var user = JSON.parse('{{ Marshal .User }}'), webdavURL = "{{.Config.WebDavURL}}", baseURL = "{{.Config.BaseURL}}";</script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js"></script>
+    <script src="{{ .Config.AbsoluteURL }}/_filemanagerinternal/js/form2js.js"></script>
+    <script src="{{ .Config.AbsoluteURL }}/_filemanagerinternal/js/application.js"></script>
+    {{ if .Config.HugoEnabled }}<script src="{{ .Config.AbsoluteURL }}/_hugointernal/js/application.js"></script>{{ end }}
+</body>
 </html>

From 1f3d2100185bcee3bb20086b0df0893fb662547b Mon Sep 17 00:00:00 2001
From: Henrique Dias <hacdias@gmail.com>
Date: Tue, 25 Oct 2016 21:13:25 +0100
Subject: [PATCH 28/28] don't display toolbar "selected" text when the device
 is small

---
 _embed/public/css/styles.css | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/_embed/public/css/styles.css b/_embed/public/css/styles.css
index 3368741b..511378e5 100644
--- a/_embed/public/css/styles.css
+++ b/_embed/public/css/styles.css
@@ -473,8 +473,8 @@ header {
     z-index: 999;
     padding: 1.7em 0;
     background-color: #2196f3;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.075);
-    box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
+    border-bottom: 1px solid rgba(0,0,0,0.075);
+    box-shadow: 0 0 5px rgba(0,0,0,0.1);
 }
 
 header h1 {
@@ -1186,4 +1186,10 @@ i.spin {
         column-count: 1;
         column-gap: 0;
     }
-}
\ No newline at end of file
+}
+
+@media screen and (max-width: 450px) {
+    #toolbar p {
+        display: none;
+    }
+}