From 44065cfaf9eb95d61a56e3d010c23c64eb939866 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 30 Oct 2016 20:42:56 +0000 Subject: [PATCH] Issue #32: working live with WebSockets! Needs to be reviewed. --- _embed/public/css/styles.css | 16 +++- _embed/public/js/application.js | 48 ++++++----- file/info.go | 2 +- filemanager.go | 44 +++------- handlers/command.go | 146 ++++++++++++++++++++++++++++++-- 5 files changed, 190 insertions(+), 66 deletions(-) diff --git a/_embed/public/css/styles.css b/_embed/public/css/styles.css index 511378e5..88b67f77 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 { @@ -548,7 +548,7 @@ header p i { min-width: 20em; border: 0; outline: 0; - color: #fff; + color: rgba(255, 255, 255, 0.72); background-color: transparent; } @@ -574,14 +574,22 @@ header p i { transition: .1s ease all; visibility: hidden; opacity: 0; + overflow-x: hidden; + overflow-y: auto; + max-height: 50vh; + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; word-wrap: break-word; } #search.active div i, #sidebar #search.active div i { color: #ccc; - display: block; text-align: center; + margin: 0 auto; + display: table; } #search::-webkit-input-placeholder { diff --git a/_embed/public/js/application.js b/_embed/public/js/application.js index 50af4f31..f8f5bc36 100644 --- a/_embed/public/js/application.js +++ b/_embed/public/js/application.js @@ -503,29 +503,18 @@ var searchEvent = function(event) { if (event.keyCode == 13) { box.innerHTML = 'autorenew'; - let request = new XMLHttpRequest(); - request.open('POST', window.location); - request.setRequestHeader('Command', value); - request.setRequestHeader('Token', token); - request.send(); - request.onreadystatechange = function() { - if (request.readyState == 4) { - if (request.status == 501) { - box.innerHTML = "Command not implemented." - } + var conn = new WebSocket('ws://' + window.location.host + window.location.pathname + '?command=true'); + conn.onopen = function() { + conn.send(value); + }; - if (request.status == 500) { - box.innerHTML = "Something went wrong." - } + conn.onmessage = function(event) { + box.innerHTML = event.data + box.scrollTop = box.scrollHeight; + } - if (request.status == 200) { - let text = request.responseText; - text = text.substring(1, text.length - 1); - text = text.replace('\\n', "\n"); - box.innerHTML = text; - reloadListing(); - } - } + conn.onclose = function(event) { + reloadListing(); } } } @@ -554,13 +543,28 @@ document.addEventListener('listing', event => { }); if (user.AllowCommands) { + let hover = false, focus = false; + document.querySelector('#search input').addEventListener('focus', event => { + focus = true; + document.getElementById('search').classList.add('active'); + }); + + document.querySelector('#search div').addEventListener('mouseover', event => { + hover = true; document.getElementById('search').classList.add('active'); }); document.querySelector('#search input').addEventListener('blur', event => { + focus = false; + if (hover) return; + document.getElementById('search').classList.remove('active'); + }); + + document.querySelector('#search').addEventListener('mouseleave', event => { + hover = false; + if (focus) return; document.getElementById('search').classList.remove('active'); - document.querySelector('#search input').value = ''; }); document.querySelector('#search div').innerHTML = "Write one of yours suported commands: " + user.Commands.join(", ") + "."; diff --git a/file/info.go b/file/info.go index bec3901e..0e55b310 100644 --- a/file/info.go +++ b/file/info.go @@ -42,7 +42,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, errors.ErrorToHTTPCode(err, true), err + return i, errors.ErrorToHTTPCode(err, false), err } return i, 0, nil diff --git a/filemanager.go b/filemanager.go index 8827e772..2d886837 100644 --- a/filemanager.go +++ b/filemanager.go @@ -67,6 +67,16 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err user = c.User } + if r.URL.Query().Get("command") != "" { + return handlers.Command(w, r, c, user) + } + + // 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 + // } + // Checks if the request URL is for the WebDav server if strings.HasPrefix(r.URL.Path, c.WebDavURL) { // if !c.CheckToken(r) { @@ -126,23 +136,6 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err 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 { @@ -178,23 +171,6 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, 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 handlers.Command(w, r, c, user) - } - } - return http.StatusNotImplemented, nil } diff --git a/handlers/command.go b/handlers/command.go index e2690c42..b1266aa1 100644 --- a/handlers/command.go +++ b/handlers/command.go @@ -1,18 +1,122 @@ package handlers import ( + "bytes" + "fmt" "net/http" "os/exec" "path/filepath" "strings" + "time" + "github.com/gorilla/websocket" "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/page" ) +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + // 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"), " ") + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + fmt.Println(err) + return 0, nil + } + defer conn.Close() + + for { + _, message, err := conn.ReadMessage() + if err != nil { + fmt.Println("read:", err) + break + } + + command := strings.Split(string(message), " ") + + if len(command) == 0 { + continue + } + // Check if the command is allowed + mayContinue := false + + for _, cmd := range u.Commands { + if cmd == command[0] { + mayContinue = true + } + } + + if !mayContinue { + err = conn.WriteMessage(websocket.BinaryMessage, []byte("FORBIDDEN")) + if err != nil { + fmt.Println("write:", err) + break + } + + return 0, nil + } + + // Check if the program is talled is installed on the computer + if _, err = exec.LookPath(command[0]); err != nil { + err = conn.WriteMessage(websocket.BinaryMessage, []byte("Command not implemented.")) + if err != nil { + fmt.Println("write:", err) + break + } + + return http.StatusNotImplemented, nil + } + + path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1) + path = filepath.Clean(path) + + buff := new(bytes.Buffer) + + cmd := exec.Command(command[0], command[1:len(command)]...) + cmd.Dir = path + cmd.Stderr = buff + cmd.Stdout = buff + err = cmd.Start() + if err != nil { + return http.StatusInternalServerError, err + } + + done := false + go func() { + err = cmd.Wait() + done = true + }() + + for !done { + by := buff.Bytes() + if len(by) > 0 { + err = conn.WriteMessage(websocket.TextMessage, by) + if err != nil { + fmt.Println("write:", err) + break + } + } + + time.Sleep(100 * time.Millisecond) + } + + by := buff.Bytes() + if len(by) > 0 { + err = conn.WriteMessage(websocket.TextMessage, by) + if err != nil { + fmt.Println("write:", err) + break + } + } + + time.Sleep(100 * time.Millisecond) + + break + } + + /* command := strings.Split(r.Header.Get("command"), " ") // Check if the command is allowed mayContinue := false @@ -37,12 +141,44 @@ func Command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config cmd := exec.Command(command[0], command[1:len(command)]...) cmd.Dir = path - output, err := cmd.CombinedOutput() + cmd.Stderr = w + cmd.Stdout = w + cmd.Start() + /*cmd.Stderr = b + cmd.Stdout = b + + // Starts the comamnd + err := cmd.Start() if err != nil { return http.StatusInternalServerError, err } - p := &page.Page{Info: &page.Info{Data: string(output)}} - return p.PrintAsJSON(w) + done := false + go func() { + err = cmd.Wait() + done = true + }() + + for !done { + by := b.Bytes() + if len(by) > 0 { + fmt.Println(string(by)) + } + + //w.Write(by) + + }*/ + + //out, err := cmd.CombinedOutput() + //fmt.Println(string(out)) + + //if err != nil { + // return http.StatusInternalServerError, err + //} + + /* cmd.Wait() + + //p := &page.Page{Info: &page.Info{Data: string(output)}} */ + return 0, nil }