summaryrefslogtreecommitdiff
path: root/service/static
diff options
context:
space:
mode:
authorWolfy-J <[email protected]>2018-06-10 16:44:41 +0300
committerWolfy-J <[email protected]>2018-06-10 16:44:41 +0300
commit4c292ee46f5505b00b16186e8f30e9bc1be25895 (patch)
tree818dffc7ce5e890875b147b97e298d4c7c48cbd9 /service/static
parenta62237fa5afc310453e709837e363f0bb4d7ecf3 (diff)
fs config
Diffstat (limited to 'service/static')
-rw-r--r--service/static/config.go52
-rw-r--r--service/static/config_test.go21
-rw-r--r--service/static/service.go120
3 files changed, 193 insertions, 0 deletions
diff --git a/service/static/config.go b/service/static/config.go
new file mode 100644
index 00000000..2a1f6c13
--- /dev/null
+++ b/service/static/config.go
@@ -0,0 +1,52 @@
+package static
+
+import (
+ "strings"
+ "path"
+ "os"
+ "github.com/pkg/errors"
+)
+
+// Config describes file location and controls access to them.
+type Config struct {
+ // Enables StaticFile service.
+ Enable bool
+
+ // Dir contains name of directory to control access to.
+ Dir string
+
+ // Forbid specifies list of file extensions which are forbidden for access.
+ // Example: .php, .exe, .bat, .htaccess and etc.
+ Forbid []string
+}
+
+// Forbid must return true if file extension is not allowed for the upload.
+func (cfg *Config) Forbids(filename string) bool {
+ ext := strings.ToLower(path.Ext(filename))
+
+ for _, v := range cfg.Forbid {
+ if ext == v {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Valid validates existence of directory.
+func (cfg *Config) Valid() error {
+ st, err := os.Stat(cfg.Dir)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return errors.New("root directory does not exists")
+ }
+
+ return err
+ }
+
+ if !st.IsDir() {
+ return errors.New("invalid root directory")
+ }
+
+ return nil
+}
diff --git a/service/static/config_test.go b/service/static/config_test.go
new file mode 100644
index 00000000..ce31348a
--- /dev/null
+++ b/service/static/config_test.go
@@ -0,0 +1,21 @@
+package static
+
+import (
+ "testing"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestConfig_Forbids(t *testing.T) {
+ cfg := Config{Forbid: []string{".php"}}
+
+ assert.True(t, cfg.Forbids("index.php"))
+ assert.True(t, cfg.Forbids("index.PHP"))
+ assert.True(t, cfg.Forbids("phpadmin/index.bak.php"))
+ assert.False(t, cfg.Forbids("index.html"))
+}
+
+func TestConfig_Valid(t *testing.T) {
+ assert.NoError(t, (&Config{Dir: "./"}).Valid())
+ assert.Error(t, (&Config{Dir: "./config.go"}).Valid())
+ assert.Error(t, (&Config{Dir: "./dir/"}).Valid())
+}
diff --git a/service/static/service.go b/service/static/service.go
new file mode 100644
index 00000000..916c18a2
--- /dev/null
+++ b/service/static/service.go
@@ -0,0 +1,120 @@
+package static
+
+import (
+ "github.com/sirupsen/logrus"
+ "net/http"
+ "os"
+ "path"
+ "strings"
+ rrttp "github.com/spiral/roadrunner/service/http"
+ "github.com/spiral/roadrunner/service"
+)
+
+// Name contains default service name.
+const Name = "static-server"
+
+// Service serves static files. Potentially convert into middleware?
+type Service struct {
+ // Logger is associated debug and error logger. Can be empty.
+ Logger *logrus.Logger
+
+ // server configuration (location, forbidden files and etc)
+ cfg *Config
+
+ // root is initiated http directory
+ root http.Dir
+
+ // let's service stay running
+ done chan interface{}
+}
+
+// Configure must return configure service and return true if service hasStatus enabled. Must return error in case of
+// misconfiguration. Services must not be used without proper configuration pushed first.
+func (s *Service) Configure(cfg service.Config, c service.Container) (enabled bool, err error) {
+ config := &Config{}
+ if err := cfg.Unmarshal(config); err != nil {
+ return false, err
+ }
+
+ if !config.Enable {
+ return false, nil
+ }
+
+ if err := config.Valid(); err != nil {
+ return false, err
+ }
+
+ s.cfg = config
+ s.root = http.Dir(s.cfg.Dir)
+
+ // registering as middleware
+ if h, ok := c.Get(rrttp.Name); ok >= service.StatusConfigured {
+ if h, ok := h.(*rrttp.Service); ok {
+ h.Add(s)
+ }
+ } else {
+ if s.Logger != nil {
+ s.Logger.Warningf("no http service found")
+ }
+ }
+
+ return true, nil
+}
+
+// Serve serves the service.
+func (s *Service) Serve() error {
+ s.done = make(chan interface{})
+ <-s.done
+
+ return nil
+}
+
+// Stop stops the service.
+func (s *Service) Stop() {
+ //todo: this is not safe (TODO CHECK IT?)
+ close(s.done)
+}
+
+// Handle must return true if request/response pair is handled withing the middleware.
+func (s *Service) Handle(w http.ResponseWriter, r *http.Request) bool {
+ fPath := r.URL.Path
+ if !strings.HasPrefix(fPath, "/") {
+ fPath = "/" + fPath
+ }
+ fPath = path.Clean(fPath)
+
+ if s.cfg.Forbids(fPath) {
+ if s.Logger != nil {
+ s.Logger.Warningf("attempt to access forbidden file %s", fPath)
+ }
+ return false
+ }
+
+ f, err := s.root.Open(fPath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ if s.Logger != nil {
+ s.Logger.Error(err)
+ }
+ }
+
+ return false
+ }
+ defer f.Close()
+
+ d, err := f.Stat()
+ if err != nil {
+ if s.Logger != nil {
+ s.Logger.Error(err)
+ }
+ return false
+ }
+
+ // do not Handle directories
+ if d.IsDir() {
+ return false
+ }
+
+ http.ServeContent(w, r, d.Name(), d.ModTime(), f)
+ return true
+}