fix (share): fix edge cases around the path and start migrate tests

This commit is contained in:
Mickael KERJEAN
2019-01-28 01:09:45 +11:00
parent 4e15d5cbdb
commit c61f528e24
20 changed files with 146 additions and 625 deletions
-48
View File
@@ -1,48 +0,0 @@
const p = require("./path");
describe("Path", () => {
it("can create beautiful with proper convention for folder or file", () => {
let res = p.pathBuilder("/test/polo", "polo", "file");
expect(res).toBe("/test/polo/polo");
res = p.pathBuilder("/test/polo", "polo", "directory");
expect(res).toBe("/test/polo/polo/");
});
it("can extract the filename from a path", () => {
let res = p.basename("/var/www/html/test")
expect(res).toBe("test");
});
it("can extract the dirname from a path", () => {
let res = p.basename("/var/www/html/test/")
expect(res).toBe("test");
});
it("can transform 2 absolute path as relative", () => {
let res = p.absoluteToRelative("/var/www/", "/var/www/html/test");
expect(res).toBe("./html/test");
res = p.absoluteToRelative("/var/www/html/test/", "/var/www/polo");
expect(res).toBe("../../polo")
res = p.absoluteToRelative("/var/www/html/test", "/var/www/polo");
expect(res).toBe("../polo")
res = p.absoluteToRelative("/var/www/html/test", "/var/www/polo/");
expect(res).toBe("../polo/")
res = p.absoluteToRelative("/var/www/", "/var/www/html/test/");
expect(res).toBe("./html/test/");
res = p.absoluteToRelative("/var/www/", "/var/www/");
expect(res).toBe("./");
res = p.absoluteToRelative("/var/www", "/var/www/");
expect(res).toBe("./www/");
res = p.absoluteToRelative("/var/test/", "/var/www");
expect(res).toBe("../www");
});
});
+4 -4
View File
@@ -161,12 +161,12 @@ export class ShareComponent extends React.Component {
render(){
const beautifulPath = function(from, to){
to = from.replace(/\/$/, "") + to;
if(filetype(from) === "directory"){
from = from.split("/");
from = from.slice(0, from.length - 1);
from = from.join("/");
from = from.split("/")
from = from.slice(0, from.length - 1)
from = from.join("/")
}
let p = absoluteToRelative(from, to);
return p.length < to.length ? p : to;
};
-2
View File
@@ -67,7 +67,6 @@ export class ViewerPage extends React.Component {
Files.options(this.state.path)
]).then((d) => {
const [content, options] = d;
console.log(options);
options.allowed
this.setState({
content: content,
@@ -75,7 +74,6 @@ export class ViewerPage extends React.Component {
acl: options["allow"]
});
}).catch((err) => {
console.log(err);
if(err && err.code === 'BINARY_FILE'){
this.setState({opener: 'download', loading: false});
}else{
+7 -7
View File
@@ -122,13 +122,13 @@ export class IDE extends React.Component {
</DropdownButton>
<DropdownList>
<DropdownItem name="na"><a download={this.props.filename} href={this.props.url}>Save current file</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/text/html"+this.props.path}>Export as HTML</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/application/pdf"+this.props.path}>Export as PDF</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/text/plain"+this.props.path}>Export as Text</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "tex")} href={"/api/export/"+(currentShare() || "me")+"/text/x-latex"+this.props.path}>Export as Latex</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "ics")} href={"/api/export/"+(currentShare() || "me")+"/text/calendar"+this.props.path}>Export as Calendar</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "pdf")} href={"/api/export/"+(currentShare() || "me")+"/application/pdf"+this.props.path+"?mode=beamer"}>Export as Beamer</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "odt")} href={"/api/export/"+(currentShare() || "me")+"/application/vnd.oasis.opendocument.text"+this.props.path}>Export as Open office</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/html"+this.props.path}>Export as HTML</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/application/pdf"+this.props.path}>Export as PDF</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/plain"+this.props.path}>Export as Text</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "tex")} href={"/api/export/"+(currentShare() || "private")+"/text/x-latex"+this.props.path}>Export as Latex</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "ics")} href={"/api/export/"+(currentShare() || "private")+"/text/calendar"+this.props.path}>Export as Calendar</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "pdf")} href={"/api/export/"+(currentShare() || "private")+"/application/pdf"+this.props.path+"?mode=beamer"}>Export as Beamer</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "odt")} href={"/api/export/"+(currentShare() || "private")+"/application/vnd.oasis.opendocument.text"+this.props.path}>Export as Open office</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/text/markdown"+this.props.path}>Export as Markdown</a></DropdownItem>
</DropdownList>
</Dropdown>
+1 -1
View File
@@ -4,7 +4,7 @@
(setq org-todo-keywords (quote ((sequence "TODO(t)" "DOING(d)" "WAITING(w)" "|" "CANCEL(C)" "DEFERRED(F)" "DONE(D)"))))
;; html export
(setq org-html-head "<meta http-equiv='X-UA-Compatible' content='IE=edge'><meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'><style>html{touch-action:manipulation;-webkit-text-size-adjust:100%}body{padding:0;margin:0;background:#f2f6fa;color:#3c495a;font-weight:normal;font-size:16px;font-family:'avenir next','avenir','San Francisco','Roboto','Arial',sans-serif}h2,h3,h4,h5,h6{font-family:'Trebuchet MS',Verdana,sans-serif;color:#586b82;padding:0;margin:20px 0 10px 0;font-size:1.2em}h2{margin:30px 0 20px 0;font-size:1.5em}h2:after{display:block;content:' ';width:60px;border-bottom:3px solid #586b82;margin-top:5px;}li{text-align:left;}a{color:#3fa7ba;text-decoration:none}p{margin:10px 0;text-align:justify}ul,ol{margin:0;text-align:justify}#content ul,#content ol{margin-top:-5px;}#content ul>li>ul, #content ol>li>ol{margin-top:0;} ul>li>code{color:#586b82}pre{white-space:pre-wrap}pre.src{padding:10px}#content{width:96%;max-width:950px;margin:2% auto 5% auto;background:white;border-radius:2px;border-right:1px solid #e2e9f0;border-bottom:2px solid #e2e9f0;padding:0 115px 150px 115px;box-sizing:border-box}.org-src-container{margin-top:50px}#postamble{opacity:0.5;padding-bottom:10px;}#postamble .author{display:none}#postamble p{text-align:center;}h1.title{background-color:#343C44;color:#fff;margin:0 -115px 50px -115px;padding:60px 0;font-weight:normal;font-size:2em;border-top-left-radius:2px;border-top-right-radius:2px}@media (max-width: 1050px){#content{padding:0 70px 100px 70px}h1.title{margin:0 -70px 50px -70px}}@media (max-width: 800px){#content{width:100%;margin-top:0;padding:0 4% 60px 4%}h1.title{margin:0 -5% 50px -5%;padding:40px 5%}}pre,.verse{box-shadow:none;background-color:#f9fbfd;border:1px solid #e2e9f0;color:#586b82;padding:10px;font-family:monospace;overflow:auto;margin:6px 0}#table-of-contents{margin-bottom:50px;margin-top:50px}#table-of-contents h2{margin-bottom:15px}#text-table-of-contents ul{padding-left:15px}#text-table-of-contents>ul{padding-left:0}#text-table-of-contents li{list-style-type:none}#text-table-of-contents a{color:#7c8ca1;font-size:0.95em;text-decoration:none}table{border-color:#586b82;font-size:0.95em}table thead{color:#586b82}table tbody tr:nth-child(even){background:#f9f9f9}table tbody tr:hover{background:#586b82!important;color:white}table .left{text-align:left}table .right{text-align:right}.todo{font-family:inherit;color:inherit}.done{color:inherit}.tag{background:initial}.tag>span{background-color:#eee;font-family:monospace;padding-left:7px;padding-right:7px;border-radius:2px;float:right;margin-left:5px;font-size:14px;padding-bottom:2px;}#text-table-of-contents .tag>span{float:none;margin-left:0}.timestamp{color:#7c8ca1}@media print{@page{margin-bottom:3cm;margin-top:3cm;margin-left:2cm;margin-right:2cm;font-size:10px}#content{border:none}}</style>")
(setq org-html-head "<meta http-equiv='X-UA-Compatible' content='IE=edge'><meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'><style>html{touch-action:manipulation;-webkit-text-size-adjust:100%}body{padding:0;margin:0;background:#f2f6fa;color:#3c495a;font-weight:normal;font-size:16px;font-family:'avenir next','avenir','San Francisco','Roboto','Arial',sans-serif}h2,h3,h4,h5,h6{font-family:'Trebuchet MS',Verdana,sans-serif;color:#586b82;padding:0;margin:20px 0 10px 0;font-size:1.2em}h2{margin:30px 0 20px 0;font-size:1.5em}h2:after{display:block;content:' ';width:60px;border-bottom:3px solid #586b82;margin-top:5px;}li{text-align:left;}a{color:#3fa7ba;text-decoration:none}p{margin:10px 0;text-align:justify}ul,ol{margin:0;text-align:justify}#content ul,#content ol{margin-top:-5px;}#content ul>li>ul, #content ol>li>ol{margin-top:0;} ul>li>code{color:#586b82}pre{white-space:pre-wrap}pre.src{padding:10px}#content{width:96%;max-width:950px;margin:2% auto 5% auto;background:white;border-radius:2px;border-right:1px solid #e2e9f0;border-bottom:2px solid #e2e9f0;padding:0 115px 150px 115px;box-sizing:border-box}.org-src-container{margin-top:50px}#postamble{opacity:0.5;padding-bottom:10px;}#postamble .author{display:none}#postamble p{text-align:center;}h1.title{background-color:#343C44;color:#fff;margin:0 -115px 50px -115px;padding:60px 0;font-weight:normal;font-size:2em;border-top-left-radius:2px;border-top-right-radius:2px}@media (max-width: 1050px){#content{padding:0 70px 100px 70px}h1.title{margin:0 -70px 50px -70px}}@media (max-width: 800px){#content{width:100%;margin-top:0;padding:0 4% 60px 4%}h1.title{margin:0 -5% 50px -5%;padding:40px 5%}}pre,.verse{box-shadow:none;background-color:#f9fbfd;border:1px solid #e2e9f0;color:#586b82;padding:10px;font-family:monospace;overflow:auto;margin:6px 0}#table-of-contents{margin-bottom:50px;margin-top:50px}#table-of-contents h2{margin-bottom:15px}#text-table-of-contents ul{padding-left:15px}#text-table-of-contents>ul{padding-left:0}#text-table-of-contents li{list-style-type:none}#text-table-of-contents a{color:#7c8ca1;font-size:0.95em;text-decoration:none}table{border-color:#586b82;font-size:0.95em}table thead{color:#586b82}table tbody tr:nth-child(even){background:#f9f9f9}table tbody tr:hover{background:#586b82!important;color:white}table .left{text-align:left}table .right{text-align:right}.todo{font-family:inherit;color:#f26d6d;opacity:0.8}.done{color:inherit}.tag{background:initial}.tag>span{background-color:#eee;font-family:monospace;padding-left:7px;padding-right:7px;border-radius:2px;float:right;margin-left:5px;font-size:14px;padding-bottom:2px;}#text-table-of-contents .tag>span{float:none;margin-left:0}.timestamp{color:#7c8ca1}@media print{@page{margin-bottom:3cm;margin-top:3cm;margin-left:2cm;margin-right:2cm;font-size:10px}#content{border:none}}</style>")
(setq org-html-validation-link nil)
(setq org-html-creator-string "Using <a href=\"http://filestash.app\">Filestash</a>")
+1 -3
View File
@@ -263,9 +263,7 @@ func JsonIterator(json string) []JSONIterator {
} else if value.IsArray() {
return true
}
if value.Value != nil {
j = append(j, JSONIterator{k, value.Value()})
}
j = append(j, JSONIterator{k, value.Value()})
return true
})
}
-45
View File
@@ -1,45 +0,0 @@
package common
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestConfigGet(t *testing.T) {
c := NewConfiguration()
assert.Equal(t, nil, c.Get("foo").Interface())
assert.Equal(t, nil, c.Get("foo.bar").Interface())
}
func TestConfigDefault(t *testing.T) {
c := NewConfiguration()
assert.Equal(t, "test", c.Get("foo.bar").Default("test").Interface())
assert.Equal(t, "test", c.Get("foo.bar").Default("test").String())
assert.Equal(t, "test", c.Get("foo.bar").String())
assert.Equal(t, "test", c.Get("foo.bar").Default("nope").String())
assert.Equal(t, "nope", c.Get("foo.bar.test").Default("nope").String())
}
func TestConfigTypeCase(t *testing.T) {
c := NewConfiguration()
assert.Equal(t, nil, c.Get("foo.bar.nil").Default(nil).Interface())
assert.Equal(t, true, c.Get("foo.bar.bool").Default(true).Bool())
assert.Equal(t, 100, c.Get("foo.bar.int").Default(100).Int())
assert.Equal(t, "test", c.Get("foo.bar.string").Default("test").String())
}
func TestConfigSet(t *testing.T) {
assert.Equal(t, "test", Config.Get("foo.bar").Set("test").String())
assert.Equal(t, "valu", Config.Get("foo.bar").Set("valu").String())
assert.Equal(t, "valu", Config.Get("foo.bar.test.bar.foo").Set("valu").String())
}
func BenchmarkGetConfigElement(b *testing.B) {
c := NewConfiguration()
c.Get("foo.bar.test.foo").Set("test")
c.Get("foo.bar.test.bar.foo").Set("valu")
for n := 0; n < b.N; n++ {
c.Get("foo.bar.test.foo").String()
}
}
-46
View File
@@ -1,46 +0,0 @@
package common
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEncryptString(t *testing.T) {
key := "test|test|test|test|test"
text := "I'm some text"
a, err := EncryptString(key, text)
assert.NoError(t, err)
assert.NotNil(t, a)
assert.NotEqual(t, a, text)
b, err := DecryptString(key, a)
assert.NoError(t, err)
assert.Equal(t, b, text)
}
func TestIDGeneration(t *testing.T) {
session := make(map[string]string)
session["foo"] = "bar"
app := &App{
Session: session,
}
id1 := GenerateID(app)
session["user"] = "polo"
id2 := GenerateID(app)
session["doesn_t_matter"] = "N/A"
id3 := GenerateID(app)
assert.NotEqual(t, id1, id2)
assert.Equal(t, id2, id3)
}
func TestStringGeneration(t *testing.T) {
str := QuickString(10)
str1 := QuickString(16)
str2 := QuickString(24)
assert.Equal(t, len(str), 10)
assert.Equal(t, len(str1), 16)
assert.Equal(t, len(str2), 24)
}
+19 -5
View File
@@ -16,17 +16,31 @@ func GetAbsolutePath(p string) string {
}
func IsDirectory(path string) bool {
if string(path[len(path)-1]) != "/" {
if path == "" {
return false
}
if path[len(path) - 1:] != "/" {
return false
}
return true
}
func JoinPath(base, file string) (string, error) {
/*
* Join 2 path together, result has a file
*/
func JoinPath(base, file string) string {
filePath := filepath.Join(base, file)
if strings.HasPrefix(filePath, base) == false {
return "", ErrNotValid
return base
}
return filePath, nil
return filePath
}
func EnforceDirectory(path string) string {
if path == "" {
return "/"
} else if path[len(path) - 1:] == "/" {
return path
}
return path + "/"
}
+28
View File
@@ -3,6 +3,7 @@ package common
import (
"bytes"
"encoding/json"
"sync"
)
func NewBool(t bool) *bool {
@@ -66,3 +67,30 @@ func PrettyPrint(json_dirty []byte) []byte {
json_pretty.Write([]byte("\n"))
return json_pretty.Bytes()
}
type SafeMapStringString struct {
sync.RWMutex
internal map[string]string
}
func NewSafeMapStringString() SafeMapStringString {
return SafeMapStringString{
internal: make(map[string]string),
}
}
func(this SafeMapStringString) Set(key string, value string) {
this.Lock()
this.internal[key] = value
this.Unlock()
}
func(this SafeMapStringString) Gets(keys ...string) []string{
this.RLock()
defer this.RUnlock()
res := make([]string, len(keys))
for i, key := range keys {
res[i] = this.internal[key]
}
return res
}
+2 -1
View File
@@ -1,6 +1,7 @@
package ctrl
import (
"encoding/json"
. "github.com/mickael-kerjean/filestash/server/common"
"io"
"io/ioutil"
@@ -91,7 +92,7 @@ func PrivateConfigUpdateHandler(ctx App, res http.ResponseWriter, req *http.Requ
func PublicConfigHandler(ctx App, res http.ResponseWriter, req *http.Request) {
cfg := Config.Export()
if c, err := Config.MarshalJSON(); err == nil {
if c, err := json.Marshal(cfg); err == nil {
hash := Hash(string(c))
if req.Header.Get("If-None-Match") == hash {
res.WriteHeader(http.StatusNotModified)
+6 -6
View File
@@ -21,6 +21,12 @@ func init() {
os.MkdirAll(EXPORT_PATH, os.ModePerm)
}
func FileExport(ctx App, res http.ResponseWriter, req *http.Request) {
http.SetCookie(res, &http.Cookie{
Name: "download",
Value: "",
MaxAge: -1,
Path: "/",
})
query := req.URL.Query()
p := mux.Vars(req)
mimeType := fmt.Sprintf("%s/%s", p["mtype0"], p["mtype1"])
@@ -32,12 +38,6 @@ func FileExport(ctx App, res http.ResponseWriter, req *http.Request) {
SendErrorResult(res, ErrPermissionDenied)
return
}
http.SetCookie(res, &http.Cookie{
Name: "download",
Value: "",
MaxAge: -1,
Path: "/",
})
var tmpPath string = EXPORT_PATH + "/export_" + QuickString(10)
var cmd *exec.Cmd
+3 -4
View File
@@ -37,6 +37,7 @@ func SessionGet(ctx App, res http.ResponseWriter, req *http.Request) {
func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {
ctx.Body["timestamp"] = time.Now().String()
session := model.MapStringInterfaceToMapStringString(ctx.Body)
session["path"] = EnforceDirectory(session["path"])
backend, err := model.NewBackend(&ctx, session)
if err != nil {
@@ -60,9 +61,9 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {
}
}
home, err := model.GetHome(backend, ctx.Session["path"])
home, err := model.GetHome(backend, session["path"])
if err != nil {
SendErrorResult(res, err)
SendErrorResult(res, ErrInvalidPassword)
return
}
@@ -88,8 +89,6 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {
if home == "" {
SendSuccessResult(res, nil)
} else if ctx.Body["path"] != nil {
SendSuccessResult(res, nil)
} else {
SendSuccessResult(res, home)
}
+16 -2
View File
@@ -11,20 +11,34 @@ import (
)
func ShareList(ctx App, res http.ResponseWriter, req *http.Request) {
path, err := pathBuilder(ctx, req.URL.Query().Get("path"))
if err != nil {
SendErrorResult(res, err)
return
}
listOfSharedLinks, err := model.ShareList(
GenerateID(&ctx),
req.URL.Query().Get("path"),
path,
)
if err != nil {
SendErrorResult(res, err)
return
}
for i:=0; i<len(listOfSharedLinks); i++ {
listOfSharedLinks[i].Path = "/" + strings.TrimPrefix(listOfSharedLinks[i].Path, path)
}
SendSuccessResults(res, listOfSharedLinks)
}
func ShareUpsert(ctx App, res http.ResponseWriter, req *http.Request) {
share_id := mux.Vars(req)["share"]
if share_id == "private" {
SendErrorResult(res, ErrNotValid)
return
}
s := Share{
Id: mux.Vars(req)["share"],
Id: share_id,
Auth: func() string {
if ctx.Share.Id == "" {
a, err := req.Cookie(COOKIE_NAME_AUTH)
+22 -19
View File
@@ -12,22 +12,16 @@ import (
"strings"
)
var ETAGS map[string]string = make(map[string]string)
var ETAGS SafeMapStringString = NewSafeMapStringString()
func StaticHandler(_path string) func(App, http.ResponseWriter, *http.Request) {
return func(ctx App, res http.ResponseWriter, req *http.Request) {
var srcPath string
var err error
if srcPath, err = JoinPath(GetAbsolutePath(_path), req.URL.Path); err != nil {
base := GetAbsolutePath(_path)
if srcPath = JoinPath(base, req.URL.Path); srcPath == base {
http.NotFound(res, req)
return
}
if strings.HasPrefix(_path, "/") == true {
http.NotFound(res, req)
return
}
ServeFile(res, req, srcPath)
}
}
@@ -38,7 +32,6 @@ func IndexHandler(_path string) func(App, http.ResponseWriter, *http.Request) {
http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect)
return
}
srcPath := GetAbsolutePath(_path)
ServeFile(res, req, srcPath)
}
@@ -109,24 +102,31 @@ func hashFile (path string, n int) string {
func ServeFile(res http.ResponseWriter, req *http.Request, filePath string) {
zFilePath := filePath + ".gz"
tags := ETAGS.Gets(filePath, zFilePath)
etagNormal := tags[0]
etagGzip := tags[1]
if req.Header.Get("If-None-Match") != "" {
if req.Header.Get("If-None-Match") == ETAGS[filePath] {
browserTag := req.Header.Get("If-None-Match")
if browserTag == etagNormal {
res.WriteHeader(http.StatusNotModified)
return
} else if req.Header.Get("If-None-Match") == ETAGS[zFilePath] {
} else if browserTag == etagGzip {
res.WriteHeader(http.StatusNotModified)
return
}
}
head := res.Header()
if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
if file, err := os.OpenFile(zFilePath, os.O_RDONLY, os.ModePerm); err == nil {
head.Set("Content-Encoding", "gzip")
if ETAGS[zFilePath] == "" {
ETAGS[zFilePath] = hashFile(zFilePath, 10)
if etagGzip == "" {
tag := hashFile(zFilePath, 10)
ETAGS.Set(zFilePath, tag)
head.Set("Etag", tag)
} else {
head.Set("Etag", etagGzip)
}
head.Set("Etag", ETAGS[zFilePath])
io.Copy(res, file)
return
}
@@ -137,9 +137,12 @@ func ServeFile(res http.ResponseWriter, req *http.Request, filePath string) {
http.NotFound(res, req)
return
}
if ETAGS[filePath] == "" {
ETAGS[filePath] = hashFile(filePath, 10)
if etagNormal == "" {
tag := hashFile(filePath, 10)
ETAGS.Set(filePath, tag)
head.Set("Etag", tag)
} else {
head.Set("Etag", etagNormal)
}
head.Set("Etag", ETAGS[filePath])
io.Copy(res, file)
}
-1
View File
@@ -62,7 +62,6 @@ func Init(a *App) {
middlewares = []Middleware{ IndexHeaders }
r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
// API for File management
files := r.PathPrefix("/api/files").Subrouter()
middlewares = []Middleware{ ApiHeaders, SecureHeaders, SessionStart, LoggedInOnly }
+27 -16
View File
@@ -7,6 +7,7 @@ import (
"github.com/mickael-kerjean/filestash/server/model"
"github.com/gorilla/mux"
"net/http"
"regexp"
"strings"
)
@@ -73,6 +74,10 @@ func RedirectSharedLoginIfNeeded(fn func(App, http.ResponseWriter, *http.Request
return func(ctx App, res http.ResponseWriter, req *http.Request) {
share_id := _extractShareId(req)
if share_id == "" {
if mux.Vars(req)["share"] == "private" {
fn(ctx, res, req)
return
}
SendErrorResult(res, ErrNotValid)
return
}
@@ -108,6 +113,7 @@ func CanManageShare(fn func(App, http.ResponseWriter, *http.Request)) func(ctx A
// In a scenario where the shared link has already been atributed, we need to make sure
// the user that's currently logged in can manage the link. 2 scenarios here:
// 1) scenario 1: the user is the very same one that generated the shared link in the first place
ctx.Share = Share{}
if ctx.Session, err = _extractSession(req, &ctx); err != nil {
SendErrorResult(res, err)
return
@@ -144,7 +150,7 @@ func _extractShareId(req *http.Request) string {
return share
}
m := mux.Vars(req)["share"]
if m == "me" {
if m == "private" {
return ""
}
return m
@@ -156,7 +162,6 @@ func _extractShare(req *http.Request) (Share, error) {
if share_id == "" {
return Share{}, nil
}
if Config.Get("features.share.enable").Bool() == false {
Log.Debug("Share feature isn't enable, contact your administrator")
return Share{}, NewError("Feature isn't enable, contact your administrator", 405)
@@ -182,37 +187,43 @@ func _extractShare(req *http.Request) (Share, error) {
func _extractSession(req *http.Request, ctx *App) (map[string]string, error) {
var str string
var err error
var res map[string]string = make(map[string]string)
var session map[string]string = make(map[string]string)
if ctx.Share.Id != "" {
str, err = DecryptString(SECRET_KEY, ctx.Share.Auth)
if err != nil {
// This typically happen when changing the secret key
return res, nil
return session, nil
}
err = json.Unmarshal([]byte(str), &res)
if ctx.Share.Path[len(ctx.Share.Path)-1:] == "/" {
res["path"] = ctx.Share.Path
err = json.Unmarshal([]byte(str), &session)
if IsDirectory(ctx.Share.Path) {
session["path"] = ctx.Share.Path
} else {
path := req.URL.Query().Get("path")
if strings.HasSuffix(ctx.Share.Path, path) == false {
return res, ErrPermissionDenied
// when the shared link is pointing to a file, we mustn't have access to the surroundings
// => we need to take extra care of which path to use as a chroot
var path string = req.URL.Query().Get("path")
if strings.HasPrefix(req.URL.Path, "/api/export/") == true {
var re = regexp.MustCompile(`^/api/export/[^\/]+/[^\/]+/[^\/]+(\/.+)$`)
path = re.ReplaceAllString(req.URL.Path, `$1`)
}
res["path"] = strings.TrimSuffix(ctx.Share.Path, path) + "/"
if strings.HasSuffix(ctx.Share.Path, path) == false {
return make(map[string]string), ErrPermissionDenied
}
session["path"] = strings.TrimSuffix(ctx.Share.Path, path) + "/"
}
return res, err
return session, err
} else {
cookie, err := req.Cookie(COOKIE_NAME_AUTH)
if err != nil {
return res, nil
return session, nil
}
str = cookie.Value
str, err = DecryptString(SECRET_KEY, str)
if err != nil {
// This typically happen when changing the secret key
return res, nil
return session, nil
}
err = json.Unmarshal([]byte(str), &res)
return res, err
err = json.Unmarshal([]byte(str), &session)
return session, err
}
}
+10 -6
View File
@@ -47,21 +47,25 @@ func NewBackend(ctx *App, conn map[string]string) (IBackend, error) {
}
func GetHome(b IBackend, base string) (string, error) {
if _, err := b.Ls(base); err != nil {
return base, err
}
if obj, ok := b.(interface{ Home() (string, error) }); ok {
absolute, err := obj.Home()
if err != nil {
return "", err
}
if strings.HasPrefix(absolute, base) == false {
return "", nil
absolute = EnforceDirectory(absolute)
base = EnforceDirectory(base)
if strings.HasPrefix(absolute, base) {
return "/" + absolute[len(base):], nil
}
return absolute[len(base):], nil
return "/", nil
}
_, err := b.Ls("/")
return base, err
return base, nil
}
func MapStringInterfaceToMapStringString(m map[string]interface{}) map[string]string {
res := make(map[string]string)
for key, value := range m {
-250
View File
@@ -1,250 +0,0 @@
package model
import (
. "github.com/mickael-kerjean/filestash/server/common"
"io/ioutil"
"strings"
"testing"
)
var app *App
func init() {
app = &App{}
}
// func TestWebdav(t *testing.T) {
// if os.Getenv("WEBDAV_URL") == "" {
// fmt.Println("- skipped webdav")
// return
// }
// b, err := NewBackend(&App{}, map[string]string{
// "type": "webdav",
// "url": os.Getenv("WEBDAV_URL"),
// })
// if err != nil {
// t.Errorf("Can't create WebDav backend")
// }
// setup(t, b)
// suite(t, b)
// tearDown(t, b)
// }
// func TestFtp(t *testing.T) {
// if os.Getenv("FTP_USERNAME") == "" || os.Getenv("FTP_PASSWORD") == "" {
// fmt.Println("- skipped ftp")
// return
// }
// b, err := NewBackend(&App{}, map[string]string{
// "type": "ftp",
// "hostname": "127.0.0.1",
// "username": os.Getenv("FTP_USERNAME"),
// "password": os.Getenv("FTP_PASSWORD"),
// })
// if err != nil {
// t.Errorf("Can't create FTP backend")
// }
// setup(t, b)
// suite(t, b)
// tearDown(t, b)
// b.Rm("/tmp/")
// }
// func TestSFtp(t *testing.T) {
// if os.Getenv("SFTP_USERNAME") == "" || os.Getenv("SFTP_PASSWORD") == "" {
// fmt.Println("- skipped sftp")
// return
// }
// b, err := NewBackend(&App{}, map[string]string{
// "type": "sftp",
// "hostname": "127.0.0.1",
// "username": os.Getenv("SFTP_USERNAME"),
// "password": os.Getenv("SFTP_PASSWORD"),
// })
// if err != nil {
// t.Errorf("Can't create SFTP backend")
// }
// setup(t, b)
// suite(t, b)
// tearDown(t, b)
// }
// func TestGit(t *testing.T) {
// if os.Getenv("GIT_USERNAME") == "" || os.Getenv("GIT_PASSWORD") == "" {
// fmt.Println("- skipped git")
// return
// }
// b, err := NewBackend(app, map[string]string{
// "type": "git",
// "repo": "https://github.com/mickael-kerjean/tmp",
// "username": os.Getenv("GIT_EMAIL"),
// "password": os.Getenv("GIT_PASSWORD"),
// })
// if err != nil {
// t.Errorf("Can't create Git backend")
// }
// setup(t, b)
// suite(t, b)
// tearDown(t, b)
// }
// func TestS3(t *testing.T) {
// b, err := NewBackend(&App{}, map[string]string{
// "type": "s3",
// "access_key_id": "Q3AM3UQ867SPQQA43P2F",
// "secret_access_key": "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
// "endpoint": "https://play.minio.io:9000/",
// })
// if err != nil {
// t.Errorf("Can't create S3 backend")
// }
// setup(t, b)
// // suite(t, b)
// //tearDown(t, b)
// }
// func TestDropbox(t *testing.T) {
// if os.Getenv("DROPBOX_TOKEN") == "" {
// fmt.Println("- skipped Dropbox")
// return
// }
// b, err := NewBackend(app, map[string]string{
// "type": "dropbox",
// "bearer": os.Getenv("DROPBOX_TOKEN"),
// })
// if err != nil {
// t.Errorf("Can't create a Dropbox backend")
// }
// setup(t, b)
// suite(t, b)
// tearDown(t, b)
// }
// func TestGoogleDrive(t *testing.T) {
// if os.Getenv("GDRIVE_TOKEN") == "" {
// fmt.Println("- skipped Google Drive")
// return
// }
// b, err := NewBackend(app, map[string]string{
// "type": "gdrive",
// "expiry": "",
// "token": os.Getenv("GDRIVE_TOKEN"),
// })
// if err != nil {
// t.Errorf("Can't create a Google Drive backend")
// }
// setup(t, b)
// suite(t, b)
// tearDown(t, b)
// }
func setup(t *testing.T, b IBackend) {
b.Rm("/tmp/test/")
b.Mkdir("/tmp/")
b.Mkdir("/tmp/test/")
}
func tearDown(t *testing.T, b IBackend) {
b.Rm("/tmp/test/")
}
func suite(t *testing.T, b IBackend) {
// create state
content := "lorem ipsum"
b.Mkdir("/tmp/test/trash/")
b.Touch("/tmp/test/test0.txt")
b.Save("/tmp/test/test0.txt", strings.NewReader(content))
b.Save("/tmp/test/test1.txt", strings.NewReader(content))
b.Touch("/tmp/test/test2.txt")
b.Mv("/tmp/test/test0.txt", "/tmp/test/trash/test0.txt")
// list all files
tmp0, err := b.Ls("/tmp/test/")
if err != nil {
t.Errorf("Ls error: %s", err)
return
}
if len(tmp0) != 3 {
t.Errorf("LS error: got: %d elmnt, want: %d", len(tmp0), 3)
return
}
// read file
tmp1, err := b.Cat("/tmp/test/trash/test0.txt")
if err != nil {
t.Errorf("Cat error: %s", err)
return
}
tmp2, err := ioutil.ReadAll(tmp1)
if err != nil {
t.Errorf("Cat error: %s", err)
return
}
if string(tmp2) != content {
t.Errorf("Incorrect file: %s, want: %s.", tmp2, content)
return
}
if obj, ok := tmp1.(interface{ Close() error }); ok {
obj.Close()
}
tmp1, err = b.Cat("/tmp/test/test1.txt")
if err != nil {
t.Errorf("Cat error: %s", err)
return
}
tmp2, err = ioutil.ReadAll(tmp1)
if err != nil {
t.Errorf("Cat error: %s", err)
return
}
if string(tmp2) != content {
t.Errorf("Incorrect file: %s, want: %s.", tmp2, content)
return
}
if obj, ok := tmp1.(interface{ Close() error }); ok {
obj.Close()
}
tmp1, err = b.Cat("/tmp/test/test2.txt")
if err != nil {
t.Errorf("Cat error: %s", err)
return
}
tmp2, err = ioutil.ReadAll(tmp1)
if err != nil {
t.Errorf("Cat error: %s", err)
return
}
if string(tmp2) != "" {
t.Errorf("Incorrect file: %s, want: %s.", tmp2, "")
return
}
if obj, ok := tmp1.(interface{ Close() error }); ok {
obj.Close()
}
// remove file
b.Rm("/tmp/test/test2.txt")
tmp0, err = b.Ls("/tmp/test/")
if len(tmp0) != 2 {
t.Errorf("Test folder elements, got: %d, want: %d.", len(tmp0), 2)
return
}
tmp0, err = b.Ls("/tmp/test/")
if err != nil {
t.Errorf("Ls error %s", err)
return
}
if len(tmp0) != 2 {
t.Errorf("LS error: got: %d elmnt, want: %d", len(tmp0), 2)
return
}
// remove folder
b.Rm("/tmp/test/")
tmp0, err = b.Ls("/tmp/test/")
if err == nil {
t.Errorf("Removed folder still exists: %d", len(tmp0))
return
}
}
-159
View File
@@ -1,159 +0,0 @@
package model
import (
"testing"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/stretchr/testify/assert"
)
var shareObj = Share{
Backend: "foo",
Id: "foo",
Path: "/var/www/",
Password: NewString("password"),
Users: nil,
CanRead: true,
CanManageOwn: true,
CanShare: true,
Expire: func() *int64{
a := int64(1537759505787)
return &a
}(),
}
//////////////////////////////////////////////
//// UPSERT
func TestShareSimpleUpsert(t *testing.T) {
err := ShareUpsert(&shareObj);
assert.NoError(t, err)
}
func TestShareMultipleUpsert(t *testing.T) {
err := ShareUpsert(&shareObj);
assert.NoError(t, err)
err = ShareUpsert(&shareObj);
assert.NoError(t, err)
_, err = ShareGet(shareObj.Id)
assert.NoError(t, err)
}
func TestShareUpsertIsProperlyInserted(t *testing.T) {
err := ShareUpsert(&shareObj);
assert.NoError(t, err)
var obj Share
obj.Id = "foo"
obj, err = ShareGet(obj.Id)
assert.NoError(t, err)
assert.NotNil(t, obj.Password)
}
//////////////////////////////////////////////
//// get
func TestShareGetNonExisting(t *testing.T) {
var s Share = shareObj
s.Id = "nothing"
_, err := ShareGet(s.Id);
assert.Error(t, err, "Shouldn't be able to get something that doesn't exist yet")
}
func TestShareGetExisting(t *testing.T) {
err := ShareUpsert(&shareObj);
assert.NoError(t, err, "Upsert issue")
_, err = ShareGet(shareObj.Id);
assert.NoError(t, err)
}
func TestShareGetExistingMakeSureDataIsOk(t *testing.T) {
err := ShareUpsert(&shareObj);
assert.NoError(t, err, "Upsert issue")
var obj Share
obj.Id = "foo"
obj.Backend = shareObj.Backend
obj, err = ShareGet(obj.Id);
assert.NoError(t, err)
assert.Equal(t, "foo", obj.Id)
assert.Equal(t, "/var/www/", obj.Path)
assert.Equal(t, true, obj.CanManageOwn)
assert.Equal(t, true, obj.CanShare)
assert.Equal(t, true, obj.CanRead)
assert.Equal(t, false, obj.CanWrite)
assert.Equal(t, false, obj.CanUpload)
assert.Equal(t, "foo", obj.Backend)
assert.Equal(t, shareObj.Expire, obj.Expire)
}
//////////////////////////////////////////////
//// LIST
func TestShareListAll(t *testing.T) {
// Initialise test
err := ShareUpsert(&shareObj);
assert.NoError(t, err, "Upsert issue")
// Actual test
list, err := ShareList(shareObj.Backend, shareObj.Path)
assert.NoError(t, err)
assert.Len(t, list, 1)
assert.NotNil(t, list[0].Password)
}
//////////////////////////////////////////////
//// DELETE
func TestShareDeleteShares(t *testing.T) {
// Initialise test
err := ShareUpsert(&shareObj);
assert.NoError(t, err, "Upsert issue")
shareObj, err = ShareGet(shareObj.Id)
assert.NoError(t, err)
// Actual Test
err = ShareDelete(shareObj.Id);
assert.NoError(t, err)
_, err = ShareGet(shareObj.Id)
assert.Error(t, err)
}
//////////////////////////////////////////////
//// PROOF
func TestShareVerifyEquivalence(t *testing.T) {
p1 := Proof {
Key: "password",
Value: "I'm something random",
}
p2 := Proof {
Key: p1.Key,
Id: "hash",
}
res := shareProofAreEquivalent(p1, p2)
assert.Equal(t, false, res)
p2.Id = Hash(p1.Key + "::" + p1.Value)
res = shareProofAreEquivalent(p1, p2)
assert.Equal(t, true, res)
p2.Key = "email"
res = shareProofAreEquivalent(p1, p2)
assert.Equal(t, false, res)
p1.Key = "email"
p1.Value = "test@gmail.com,polo@gmail.com,jean@gmail.com"
p2.Key = "email"
p2.Id = Hash(p1.Key + "::" + "polo@gmail.com")
res = shareProofAreEquivalent(p1, p2)
assert.Equal(t, true, res)
}