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}}
|
|
`
|