412 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			412 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package template
 | |
| 
 | |
| var filesTemplate = `{{buildTags .Tags}}// Code generated by fileb0x at "{{.Now}}" from config file "{{.ConfigFile}}" DO NOT EDIT.
 | |
| // modification hash({{.ModificationHash}})
 | |
| 
 | |
| package {{.Pkg}}
 | |
| {{$Compression := .Compression}}
 | |
| 
 | |
| import (
 | |
|   "bytes"
 | |
|   {{if not .Spread}}{{if and $Compression.Compress (not .Debug)}}{{if not $Compression.Keep}}"compress/gzip"{{end}}{{end}}{{end}}
 | |
|   "context"
 | |
|   "io"
 | |
|   "net/http"
 | |
|   "os"
 | |
|   "path"
 | |
| {{if or .Updater.Enabled .Debug}}
 | |
|   "strings"
 | |
| {{end}}
 | |
| 
 | |
|   "golang.org/x/net/webdav"
 | |
| 
 | |
| {{if .Updater.Enabled}}
 | |
|   "crypto/sha256"
 | |
| 	"encoding/hex"
 | |
|   "log"
 | |
|   "path/filepath"
 | |
| 
 | |
| 	"github.com/labstack/echo"
 | |
| 	"github.com/labstack/echo/middleware"
 | |
| {{end}}
 | |
| )
 | |
| 
 | |
| var ( 
 | |
|   // CTX is a context for webdav vfs
 | |
|   {{exported "CTX"}} = context.Background()
 | |
| 
 | |
|   {{if .Debug}}
 | |
|   {{exported "FS"}} = webdav.Dir(".")
 | |
|   {{else}}
 | |
|   // FS is a virtual memory file system
 | |
|   {{exported "FS"}} = webdav.NewMemFS()
 | |
|   {{end}}
 | |
| 
 | |
|   // Handler is used to server files through a http handler
 | |
|   {{exportedTitle "Handler"}} *webdav.Handler
 | |
| 
 | |
|   // HTTP is the http file system
 | |
|   {{exportedTitle "HTTP"}} http.FileSystem = new({{exported "HTTPFS"}})
 | |
| )
 | |
| 
 | |
| // HTTPFS implements http.FileSystem
 | |
| type {{exported "HTTPFS"}} struct {
 | |
| 	// Prefix allows to limit the path of all requests. F.e. a prefix "css" would allow only calls to /css/*
 | |
| 	Prefix string
 | |
| }
 | |
| 
 | |
| {{if (and (not .Spread) (not .Debug))}}
 | |
| {{range .Files}}
 | |
| // {{exportedTitle "File"}}{{buildSafeVarName .Path}} is "{{.Path}}"
 | |
| var {{exportedTitle "File"}}{{buildSafeVarName .Path}} = {{.Data}}
 | |
| {{end}}
 | |
| {{end}}
 | |
| 
 | |
| func init() {
 | |
|   err := {{exported "CTX"}}.Err()
 | |
|   if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| {{ $length := len .DirList }}
 | |
| {{ $fLength := len .Files }}
 | |
| {{ $noDirsButFiles := (and (not .Spread) (eq $length 0) (gt $fLength 0)) }}
 | |
| {{if not .Debug}}
 | |
| {{range $index, $dir := .DirList}}
 | |
|   {{if and (ne $dir "./") (ne $dir "/") (ne $dir ".") (ne $dir "")}}
 | |
|   err = {{exported "FS"}}.Mkdir({{exported "CTX"}}, "{{$dir}}", 0777)
 | |
|   if err != nil && err != os.ErrExist {
 | |
|     panic(err)
 | |
|   }
 | |
|   {{end}}
 | |
| {{end}}
 | |
| {{end}}
 | |
| 
 | |
| {{if (and (not .Spread) (not .Debug))}}
 | |
|   {{if not .Updater.Empty}}
 | |
|   var f webdav.File
 | |
|   {{end}}
 | |
| 
 | |
|   {{if $Compression.Compress}}
 | |
|   {{if not $Compression.Keep}}
 | |
|   var rb *bytes.Reader
 | |
|   var r *gzip.Reader
 | |
|   {{end}}
 | |
|   {{end}}
 | |
| 
 | |
|   {{range .Files}}
 | |
|   {{if $Compression.Compress}}
 | |
|   {{if not $Compression.Keep}}
 | |
|   rb = bytes.NewReader({{exportedTitle "File"}}{{buildSafeVarName .Path}})
 | |
|   r, err = gzip.NewReader(rb)
 | |
|   if err != nil {
 | |
|     panic(err)
 | |
|   }
 | |
| 
 | |
|   err = r.Close()
 | |
|   if err != nil {
 | |
|     panic(err)
 | |
|   }
 | |
|   {{end}}
 | |
|   {{end}}
 | |
| 
 | |
|   f, err = {{exported "FS"}}.OpenFile({{exported "CTX"}}, "{{.Path}}", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
 | |
|   if err != nil {
 | |
|     panic(err)
 | |
|   }
 | |
| 
 | |
|   {{if $Compression.Compress}}
 | |
|   {{if not $Compression.Keep}}
 | |
|   _, err = io.Copy(f, r)
 | |
|   if err != nil {
 | |
|     panic(err)
 | |
|   }
 | |
|   {{end}}
 | |
|   {{else}}
 | |
|   _, err = f.Write({{exportedTitle "File"}}{{buildSafeVarName .Path}})
 | |
|   if err != nil {
 | |
|     panic(err)
 | |
|   }
 | |
|   {{end}}
 | |
| 
 | |
|   err = f.Close()
 | |
|   if err != nil {
 | |
|     panic(err)
 | |
|   }
 | |
|   {{end}}
 | |
| {{end}}
 | |
| 
 | |
|   {{exportedTitle "Handler"}} = &webdav.Handler{
 | |
|     FileSystem: FS,
 | |
|     LockSystem: webdav.NewMemLS(),
 | |
|   }
 | |
| 
 | |
| {{if .Updater.Enabled}}
 | |
|   go func() {
 | |
|     svr := &{{exportedTitle "Server"}}{}
 | |
|     svr.Init()
 | |
|   }()
 | |
| {{end}}
 | |
| }
 | |
| 
 | |
| {{if .Debug}}
 | |
| var remap = map[string]map[string]string{
 | |
|   {{.Remap}}
 | |
| }
 | |
| {{end}}
 | |
| 
 | |
| // Open a file
 | |
| func (hfs *{{exported "HTTPFS"}}) Open(path string) (http.File, error) {
 | |
|   path = hfs.Prefix + path
 | |
| 
 | |
| {{if .Debug}}
 | |
|   path = strings.TrimPrefix(path, "/")
 | |
| 
 | |
|   for current, f := range remap {
 | |
|     if path == current {
 | |
|       path = f["base"] + strings.TrimPrefix(path, f["prefix"])
 | |
|       break
 | |
|     }
 | |
|   }
 | |
| 
 | |
| {{end}}
 | |
|   f, err := {{if .Debug}}os{{else}}{{exported "FS"}}{{end}}.OpenFile({{if not .Debug}}{{exported "CTX"}}, {{end}}path, os.O_RDONLY, 0644)
 | |
|   if err != nil {
 | |
|     return nil, err
 | |
|   }
 | |
| 
 | |
|   return f, nil
 | |
| }
 | |
| 
 | |
| // ReadFile is adapTed from ioutil
 | |
| func {{exportedTitle "ReadFile"}}(path string) ([]byte, error) {
 | |
|   f, err := {{if .Debug}}os{{else}}{{exported "FS"}}{{end}}.OpenFile({{if not .Debug}}{{exported "CTX"}}, {{end}}path, os.O_RDONLY, 0644)
 | |
|   if err != nil {
 | |
|     return nil, err
 | |
|   }
 | |
| 
 | |
|   buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
 | |
| 
 | |
|   // If the buffer overflows, we will get bytes.ErrTooLarge.
 | |
|   // Return that as an error. Any other panic remains.
 | |
|   defer func() {
 | |
|     e := recover()
 | |
|     if e == nil {
 | |
|       return
 | |
|     }
 | |
|     if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
 | |
|       err = panicErr
 | |
|     } else {
 | |
|       panic(e)
 | |
|     }
 | |
|   }()
 | |
|   _, err = buf.ReadFrom(f)
 | |
|   return buf.Bytes(), err
 | |
| }
 | |
| 
 | |
| // WriteFile is adapTed from ioutil
 | |
| func {{exportedTitle "WriteFile"}}(filename string, data []byte, perm os.FileMode) error {
 | |
|   f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
 | |
|   if err != nil {
 | |
|     return err
 | |
|   }
 | |
|   n, err := f.Write(data)
 | |
|   if err == nil && n < len(data) {
 | |
|     err = io.ErrShortWrite
 | |
|   }
 | |
|   if err1 := f.Close(); err == nil {
 | |
|     err = err1
 | |
|   }
 | |
|   return err
 | |
| }
 | |
| 
 | |
| // WalkDirs looks for files in the given dir and returns a list of files in it
 | |
| // usage for all files in the b0x: WalkDirs("", false)
 | |
| func {{exportedTitle "WalkDirs"}}(name string, includeDirsInList bool, files ...string) ([]string, error) {
 | |
| 	f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, name, os.O_RDONLY, 0)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	fileInfos, err := f.Readdir(0)
 | |
| 	if err != nil {
 | |
|     return nil, err
 | |
|   }
 | |
|   
 | |
|   err = f.Close()
 | |
|   if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, info := range fileInfos {
 | |
| 		filename := path.Join(name, info.Name())
 | |
| 
 | |
| 		if includeDirsInList || !info.IsDir() {
 | |
| 			files = append(files, filename)
 | |
| 		}
 | |
| 
 | |
| 		if info.IsDir() {
 | |
| 			files, err = {{exportedTitle "WalkDirs"}}(filename, includeDirsInList, files...)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return files, nil
 | |
| }
 | |
| 
 | |
| {{if .Updater.Enabled}}
 | |
| // Auth holds information for a http basic auth
 | |
| type {{exportedTitle "Auth"}} struct {
 | |
|   Username string
 | |
|   Password string
 | |
| }
 | |
| 
 | |
| // ResponseInit holds a list of hashes from the server
 | |
| // to be sent to the client so it can check if there
 | |
| // is a new file or a changed file
 | |
| type {{exportedTitle "ResponseInit"}} struct {
 | |
|   Success bool
 | |
|   Hashes  map[string]string
 | |
| }
 | |
| 
 | |
| // Server holds information about the http server
 | |
| // used to update files remotely
 | |
| type {{exportedTitle "Server"}} struct {
 | |
|   Auth {{exportedTitle "Auth"}}
 | |
|   Files []string
 | |
| }
 | |
| 
 | |
| // Init sets the routes and basic http auth 
 | |
| // before starting the http server
 | |
| func (s *{{exportedTitle "Server"}}) Init() {
 | |
|   s.Auth = {{exportedTitle "Auth"}}{
 | |
|     Username: "{{.Updater.Username}}",
 | |
|     Password: "{{.Updater.Password}}",
 | |
|   }
 | |
| 
 | |
|   e := echo.New()
 | |
|   e.Use(middleware.Recover())
 | |
|   e.Use(s.BasicAuth())
 | |
|   e.POST("/", s.Post)
 | |
|   e.GET("/", s.Get)
 | |
| 
 | |
|   log.Println("fileb0x updater server is running at port 0.0.0.0:{{.Updater.Port}}")
 | |
|   if err := e.Start(":{{.Updater.Port}}"); err != nil {
 | |
|     panic(err)
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Get gives a list of file names and hashes
 | |
| func (s *{{exportedTitle "Server"}}) Get(c echo.Context) error {
 | |
|   log.Println("[fileb0x.Server]: Hashing server files...")
 | |
|   
 | |
|   // file:hash
 | |
|   hashes := map[string]string{}
 | |
| 
 | |
|   // get all files in the virtual memory file system
 | |
|   var err error
 | |
|   s.Files, err = {{exportedTitle "WalkDirs"}}("", false)
 | |
|   if err != nil {
 | |
|     return err
 | |
|   }
 | |
| 
 | |
|   // get a hash for each file
 | |
|   for _, filePath := range s.Files {
 | |
|     f, err := FS.OpenFile(CTX, filePath, os.O_RDONLY, 0644)
 | |
|     if err != nil {
 | |
|       return err
 | |
|     }
 | |
| 
 | |
|     hash := sha256.New()
 | |
|     _, err = io.Copy(hash, f)
 | |
|     if err != nil {
 | |
|       return err
 | |
|     }
 | |
| 
 | |
|     hashes[filePath] = hex.EncodeToString(hash.Sum(nil))
 | |
|   }
 | |
| 
 | |
|   log.Println("[fileb0x.Server]: Done hashing files")
 | |
|   return c.JSON(http.StatusOK, &ResponseInit{
 | |
|     Success: true,
 | |
|     Hashes: hashes,
 | |
|   })
 | |
| }
 | |
| 
 | |
| // Post is used to upload a file and replace 
 | |
| // it in the virtual memory file system
 | |
| func (s *{{exportedTitle "Server"}}) Post(c echo.Context) error {
 | |
|   file, err := c.FormFile("file")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
|   log.Println("[fileb0x.Server]:", file.Filename, "Found request to upload a file")
 | |
| 
 | |
| 	src, err := file.Open()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer src.Close()
 | |
| 
 | |
| 
 | |
|   newDir := filepath.Dir(file.Filename)
 | |
|   _, err = {{exported "FS"}}.Stat({{exported "CTX"}}, newDir)
 | |
|   if err != nil && strings.HasSuffix(err.Error(), os.ErrNotExist.Error()) {
 | |
|     log.Println("[fileb0x.Server]: Creating dir tree", newDir)
 | |
|     list := strings.Split(newDir, "/")
 | |
|     var tree string
 | |
|     
 | |
|     for _, dir := range list {
 | |
|       if dir == "" || dir == "." || dir == "/" || dir == "./" {
 | |
|         continue
 | |
|       }
 | |
| 
 | |
|       tree += dir + "/"
 | |
|       err = {{exported "FS"}}.Mkdir({{exported "CTX"}}, tree, 0777)
 | |
|       if err != nil && err != os.ErrExist {
 | |
|         log.Println("failed", err)
 | |
|         return err
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   log.Println("[fileb0x.Server]:", file.Filename, "Opening file...")
 | |
|   f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, file.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
 | |
|   if err != nil && !strings.HasSuffix(err.Error(), os.ErrNotExist.Error()) {
 | |
|     return err
 | |
|   }
 | |
| 
 | |
|   log.Println("[fileb0x.Server]:", file.Filename, "Writing file into Virutal Memory FileSystem...")
 | |
|   if _, err = io.Copy(f, src); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
|   if err = f.Close(); err != nil {
 | |
|     return err
 | |
|   }
 | |
| 
 | |
|   log.Println("[fileb0x.Server]:", file.Filename, "Done writing file")
 | |
|   return c.String(http.StatusOK, "ok")
 | |
| }
 | |
| 
 | |
| // BasicAuth is a middleware to check if 
 | |
| // the username and password are valid
 | |
| // echo's middleware isn't used because of golint issues
 | |
| func (s *{{exportedTitle "Server"}}) BasicAuth() echo.MiddlewareFunc {
 | |
| 	return func(next echo.HandlerFunc) echo.HandlerFunc {
 | |
| 		return func(c echo.Context) error {
 | |
| 			u, p, _ := c.Request().BasicAuth()
 | |
| 			if u != s.Auth.Username || p != s.Auth.Password {
 | |
| 				return echo.ErrUnauthorized
 | |
| 			}
 | |
| 
 | |
| 			return next(c)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| {{end}}
 | |
| `
 |