Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more conservative text editor #119

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 32 additions & 20 deletions gossa.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,6 @@ var verb = flag.Bool("verb", false, "verbosity")
var skipHidden = flag.Bool("k", true, "\nskip hidden files")
var ro = flag.Bool("ro", false, "read only mode (no upload, rename, move, etc...)")

type rpcCall struct {
Call string `json:"call"`
Args []string `json:"args"`
}

var rootPath = ""
var handler http.Handler

Expand Down Expand Up @@ -134,7 +129,7 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Type", "text/html")
w.Header().Add("Content-Encoding", "gzip")
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test, and only ~30% larger (compression remains still very effective, ~6x)
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test
check(err)
defer gz.Close()
tmpl.Execute(gz, p)
Expand All @@ -151,7 +146,8 @@ func doContent(w http.ResponseWriter, r *http.Request) {

path := html.UnescapeString(r.URL.Path)
defer exitPath(w, "get content", path)
fullPath := enforcePath(path)
fullPath, err := enforcePath(path)
check(err)
stat, errStat := os.Stat(fullPath)
check(errStat)

Expand All @@ -174,7 +170,9 @@ func upload(w http.ResponseWriter, r *http.Request) {
if err != nil && err != io.EOF { // errs EOF when no more parts to process
check(err)
}
dst, err := os.Create(enforcePath(path))
path, err = enforcePath(path)
check(err)
dst, err := os.Create(path)
check(err)
io.Copy(dst, part)
w.Write([]byte("ok"))
Expand All @@ -184,8 +182,9 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
zipPath := r.URL.Query().Get("zipPath")
zipName := r.URL.Query().Get("zipName")
defer exitPath(w, "zip", zipPath)
zipFullPath := enforcePath(zipPath)
_, err := os.Lstat(zipFullPath)
zipFullPath, err := enforcePath(zipPath)
check(err)
_, err = os.Lstat(zipFullPath)
check(err)
w.Header().Add("Content-Disposition", "attachment; filename=\""+zipName+".zip\"")
zipWriter := zip.NewWriter(w)
Expand All @@ -203,7 +202,7 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
return nil // hidden files not allowed
}
if f.Mode()&os.ModeSymlink != 0 {
panic(errors.New("symlink not allowed in zip downloads")) // filepath.Walk doesnt support symlinks
check(errors.New("symlink not allowed in zip downloads")) // filepath.Walk doesnt support symlinks
}

header, err := zip.FileInfoHeader(f)
Expand All @@ -224,39 +223,52 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
}

func rpc(w http.ResponseWriter, r *http.Request) {
var err error
type rpcCall struct {
Call string `json:"call"`
Args []string `json:"args"`
}
var rpc rpcCall
defer exitPath(w, "rpc", rpc)
bodyBytes, err := io.ReadAll(r.Body)
check(err)
json.Unmarshal(bodyBytes, &rpc)

path0, err := enforcePath(rpc.Args[0])
path1 := ""
check(err)
if len(rpc.Args) > 1 {
path1, err = enforcePath(rpc.Args[1])
check(err)
}

if rpc.Call == "mkdirp" {
err = os.MkdirAll(enforcePath(rpc.Args[0]), os.ModePerm)
} else if rpc.Call == "mv" {
err = os.Rename(enforcePath(rpc.Args[0]), enforcePath(rpc.Args[1]))
err = os.MkdirAll(path0, os.ModePerm)
} else if rpc.Call == "mv" && len(rpc.Args) == 2 {
err = os.Rename(path0, path1)
} else if rpc.Call == "rm" {
err = os.RemoveAll(enforcePath(rpc.Args[0]))
err = os.RemoveAll(path0)
} else {
err = errors.New("invalid rpc call")
}

check(err)
w.Write([]byte("ok"))
}

func enforcePath(p string) string {
func enforcePath(p string) (string, error) {
joined := filepath.Join(rootPath, strings.TrimPrefix(p, *extraPath))
fp, err := filepath.Abs(joined)
sl, _ := filepath.EvalSymlinks(fp) // err skipped as it would error for unexistent files (RPC check). The actual behaviour is tested below
sl, _ := filepath.EvalSymlinks(fp) // err skipped as it would error for inexistent files (RPC check). The actual behaviour is tested below

// panic if we had a error getting absolute path,
// ... or if path doesnt contain the prefix path we expect,
// ... or if we're skipping hidden folders, and one is requested,
// ... or if we're skipping symlinks, path exists, and a symlink out of bound requested
if err != nil || !strings.HasPrefix(fp, rootPath) || *skipHidden && strings.Contains(p, "/.") || !*symlinks && len(sl) > 0 && !strings.HasPrefix(sl, rootPath) {
panic(errors.New("invalid path"))
return "", errors.New("invalid path")
}

return fp
return fp, nil
}

func main() {
Expand Down
3 changes: 2 additions & 1 deletion test-fixture/b.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
B!!!
B!!!
test
35 changes: 23 additions & 12 deletions ui/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,11 @@ function rpc (call, args, cb) {
xhr.open('POST', location.origin + window.extraPath + '/rpc')
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
xhr.send(JSON.stringify({ call, args }))
xhr.onload = cb
xhr.onerror = () => flicker(sadBadge)
xhr.onload = () => cb(false)
xhr.onerror = () => {
flicker(sadBadge)
cb(true)
}
}

const mkdirCall = (path, cb) => rpc('mkdirp', [prependPath(path)], cb)
Expand Down Expand Up @@ -315,28 +318,36 @@ const textTypes = ['.txt', '.rtf', '.md', '.markdown', '.log', '.yaml', '.yml']
const isTextFile = src => src && textTypes.find(type => src.toLocaleLowerCase().includes(type))
let fileEdited

function saveText (quitting) {
function saveText (cb) {
const formData = new FormData()
formData.append(fileEdited, editor.value)
const path = encodeURIComponent(decodeURI(location.pathname) + fileEdited)
const fname = fileEdited + ".swp"
const path = encodeURIComponent(decodeURI(location.pathname) + fname)
upload(0, formData, path, () => {
toast.style.display = 'none'
if (!quitting) return
clearInterval(window.padTimer)
window.onbeforeunload = null
resetView()
softPrev()
refresh()
cb()
}, () => {
toast.style.display = 'block'
if (!quitting) return
alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
})
}

function padOff () {
if (!isEditorMode()) { return }
saveText(true)
const swapfile = fileEdited + ".swp"
saveText(() => {
mvCall(prependPath(swapfile), prependPath(fileEdited), err => {
if (err) {
alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
return
}
clearInterval(window.padTimer)
window.onbeforeunload = null
resetView()
softPrev()
refresh()
})
})
return true
}

Expand Down
Loading