Added static router for file serving
This commit is contained in:
+2
-2
@@ -18,8 +18,8 @@ type Resource struct {
|
|||||||
delete IDFunc
|
delete IDFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResource(list, create Func, get, update, delete IDFunc) Resource {
|
func NewResource(list, create Func, get, update, delete IDFunc) *Resource {
|
||||||
return Resource{list, create, get, update, delete}
|
return &Resource{list, create, get, update, delete}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resource *Resource) Handle(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
|
func (resource *Resource) Handle(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
|
||||||
|
|||||||
@@ -73,6 +73,14 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (router *Router) Resource(mount string, list, create Func, get, update, delete IDFunc) {
|
||||||
|
router.Route(mount, NewResource(list, create, get, update, delete))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (router *Router) Static(mount string, filePath string) {
|
||||||
|
router.Route(mount, NewStatic(filePath))
|
||||||
|
}
|
||||||
|
|
||||||
func (router *Router) Listen(host string, port int) error {
|
func (router *Router) Listen(host string, port int) error {
|
||||||
return http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), router)
|
return http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), router)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package wrouter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.aiterp.net/gisle/wrouter/response"
|
||||||
|
|
||||||
|
"git.aiterp.net/gisle/wrouter/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Static struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatic(path string) *Static {
|
||||||
|
return &Static{path}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (static *Static) Handle(urlPath string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
|
||||||
|
// Get the subpath out of the path
|
||||||
|
subpath := req.URL.Path[len(urlPath):]
|
||||||
|
if subpath[0] == '/' {
|
||||||
|
subpath = subpath[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow breaking out of the folder
|
||||||
|
if strings.Contains(subpath, "..") {
|
||||||
|
response.Text(w, 403, "No .. in paths allowed")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading the file
|
||||||
|
filepath := path.Join(static.path, subpath)
|
||||||
|
info, err := os.Stat(filepath)
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
file, err := os.Open(filepath)
|
||||||
|
if err != nil || file == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and convert extension
|
||||||
|
ep := strings.LastIndex(filepath, ".")
|
||||||
|
ext := ""
|
||||||
|
if ep != -1 {
|
||||||
|
ext = filepath[ep:]
|
||||||
|
}
|
||||||
|
mimeType := mime.TypeByExtension(ext)
|
||||||
|
if mimeType == "" {
|
||||||
|
mimeType = "text/plain"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", mimeType)
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
w.WriteHeader(200)
|
||||||
|
io.Copy(w, file)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package wrouter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatic(t *testing.T) {
|
||||||
|
router := &Router{}
|
||||||
|
router.Static("/data", "./")
|
||||||
|
server := httptest.NewServer(router)
|
||||||
|
|
||||||
|
t.Run("Download", func(t *testing.T) {
|
||||||
|
resp, err := http.Get(server.URL + "/data/README.md")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Request:", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
t.Error("Expected 200, got", resp.Status)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.ContentLength == 0 {
|
||||||
|
t.Error("No content returned from server")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
|
||||||
|
t.Errorf("Content-Type %s != %s", resp.Header.Get("Content-Type"), "text/plain")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Download_Fail", func(t *testing.T) {
|
||||||
|
resp, err := http.Get(server.URL + "/data/f42klfk2kf2kfk.md")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Request:", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 404 {
|
||||||
|
t.Error("Expected 404, got", resp.Status)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Download_Fail2", func(t *testing.T) {
|
||||||
|
resp, err := http.Get(server.URL + "/data/../../../../../../../etc/passwd")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Request:", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 403 {
|
||||||
|
t.Error("Expected 403, got", resp.Status)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user