Compare commits
37 Commits
Author | SHA1 | Date |
---|---|---|
Aaron Bieber | e3b3dad3ee | |
Aaron Bieber | 14bdf17e49 | |
Aaron Bieber | 0ea90c5ddd | |
Aaron Bieber | a86c07b5e8 | |
Aaron Bieber | d11f802f05 | |
Aaron Bieber | e0ee714d3e | |
Aaron Bieber | db7f06dc5b | |
Aaron Bieber | 68c586f805 | |
Aaron Bieber | 8c113d95c1 | |
Aaron Bieber | 1112c9874e | |
Aaron Bieber | 18a4ed5026 | |
Aaron Bieber | f9de42e801 | |
Aaron Bieber | 4fdac0116f | |
Aaron Bieber | 0205c58868 | |
Ben Tsai | 4e224c9c08 | |
Ysbrand van Eijck | fc9da8e1f3 | |
Ysbrand van Eijck | 3ddc711cc1 | |
Aaron Bieber | 6e2f8a7495 | |
Aaron Bieber | 2551d2d9a0 | |
Aaron Bieber | 3fb82cec02 | |
Aaron Bieber | 574110c52f | |
Aaron Bieber | 3849f2ba51 | |
Aaron Bieber | 3deceffa01 | |
Aaron Bieber | 7f03d5e16e | |
Aaron Bieber | 7b16296562 | |
Aaron Bieber | 34876ea93d | |
Aaron Bieber | 0c90ee2be7 | |
Aaron Bieber | 2cfd965467 | |
Aaron Bieber | 8f69c90a69 | |
Aaron Bieber | 85d4c11447 | |
Aaron Bieber | 62ad6c78d6 | |
Aaron Bieber | 4ce5ad6a00 | |
Aaron Bieber | e38b3cbc23 | |
Aaron Bieber | 490a1a757a | |
Aaron Bieber | b3b8aa563d | |
Aaron Bieber | 769dac74ea | |
Aaron Bieber | 723e91e05d |
|
@ -1 +1,3 @@
|
|||
widdler
|
||||
.htpasswd
|
||||
dist/
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go vet
|
||||
- staticcheck
|
||||
- gosec .
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- openbsd_amd64
|
||||
- openbsd_arm64
|
||||
- openbsd_386
|
||||
- openbsd_arm
|
||||
- freebsd_amd64
|
||||
- freebsd_arm64
|
||||
- freebsd_386
|
||||
- darwin_amd64
|
||||
- darwin_arm64
|
||||
- windows_amd64
|
||||
- netbsd_amd64
|
||||
- netbsd_arm64
|
||||
- netbsd_386
|
||||
- dragonfly_amd64
|
||||
goos:
|
||||
- openbsd
|
||||
- freebsd
|
||||
- netbsd
|
||||
- dragonfly
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
ldflags:
|
||||
- -s -w -X main.build={{.Version}}
|
||||
archives:
|
||||
- replacements:
|
||||
openbsd: OpenBSD
|
||||
freebsd: FreeBSD
|
||||
netbsd: NetBSD
|
||||
dragonfly: DragonflyBSD
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
46
README.md
46
README.md
|
@ -4,26 +4,45 @@ widdler
|
|||
widdler is a single binary that serves up
|
||||
[TiddlyWiki](https://tiddlywiki.com)s.
|
||||
|
||||
It can be used to server existing wikis, or to create new ones.
|
||||
It can be used to serve existing wikis, or to create new ones.
|
||||
|
||||
# Features
|
||||
|
||||
- TiddlyWikis are served over WebDav so you can save directly from the browser.
|
||||
- Automatically create new wiki files by browsing to a non-existent html file.
|
||||
- Built in .htpasswd management (Adding users).
|
||||
- Password protection via HTTP Basic Authentication.
|
||||
- Multiple users (adding another user to the .htaccess file creates a new user
|
||||
namespace).
|
||||
- Optional TLS support.
|
||||
|
||||
# Installation
|
||||
|
||||
For Go 1.16:
|
||||
```
|
||||
go get -u suah.dev/widdler
|
||||
```
|
||||
|
||||
For Go 1.17 and up:
|
||||
```
|
||||
go install suah.dev/widdler@latest
|
||||
```
|
||||
|
||||
# Running
|
||||
|
||||
```
|
||||
mkdir wiki
|
||||
cd wiki
|
||||
# OpenBSD:
|
||||
htpasswd .htpasswd youruser
|
||||
# or on Linux/macOS
|
||||
# htpasswd -c -B youruser
|
||||
widdler
|
||||
# Generate a .htpasswd file:
|
||||
widdler -gen
|
||||
Username: qbit
|
||||
Passwd: ******
|
||||
# Start the server
|
||||
./widdler
|
||||
```
|
||||
|
||||
Now open your browser to [http://localhost:8080](http://localhost:8080).
|
||||
|
||||
# Creating a new TiddlyWiki
|
||||
|
||||
Simply browse to the file name you wish to create. widdler will automatically
|
||||
|
@ -33,5 +52,16 @@ create the wiki file based off the current `empty.html` TiddlyWiki version.
|
|||
|
||||
Simply hit the save button!
|
||||
|
||||
# TODO
|
||||
- [ ] Multi-user support
|
||||
# Updating widdler
|
||||
|
||||
```
|
||||
go install suah.dev/widdler@latest
|
||||
```
|
||||
|
||||
# Running without .htpasswd
|
||||
|
||||
You can disable auth all together by setting the `-auth` flag to false:
|
||||
|
||||
```
|
||||
widdler -auth=false -wikis ~/wiki
|
||||
```
|
||||
|
|
12900
empty-5.1.23.html
12900
empty-5.1.23.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
go.mod
8
go.mod
|
@ -3,7 +3,9 @@ module suah.dev/widdler
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed
|
||||
suah.dev/protect v1.0.0
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
|
||||
golang.org/x/net v0.0.0-20211205041911-012df41ee64c
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
suah.dev/protect v1.2.0
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -1,16 +1,18 @@
|
|||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211205041911-012df41ee64c h1:7SfqwP5fxEtl/P02w5IhKc86ziJ+A25yFrkVgoy2FT8=
|
||||
golang.org/x/net v0.0.0-20211205041911-012df41ee64c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
suah.dev/protect v1.0.0 h1:X8pzDvDIZIiugmkmr6DES6JFO1XUdJWi34Ffmk6CMZY=
|
||||
suah.dev/protect v1.0.0/go.mod h1:ZSgyBM30JUwhVPWJzVHh0jlu5W6Qz1VR6tIhAzqJZ9Y=
|
||||
suah.dev/protect v1.2.0 h1:4G4V43yVYXCjLFzaE9QJR0fLo3rf5vNBS9YxyoI19DU=
|
||||
suah.dev/protect v1.2.0/go.mod h1:Ocn1yqUskqe/is6N2bxQxtT+fegbvQsOFyHbJAQu9XE=
|
||||
|
|
344
main.go
344
main.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"embed"
|
||||
"encoding/csv"
|
||||
"flag"
|
||||
|
@ -14,25 +15,82 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/net/webdav"
|
||||
"golang.org/x/term"
|
||||
"suah.dev/protect"
|
||||
)
|
||||
|
||||
var twFile = "empty-5.1.23.html"
|
||||
// Landing will be used to fill our landing template
|
||||
type Landing struct {
|
||||
User string
|
||||
URL string
|
||||
}
|
||||
|
||||
//go:embed empty-5.1.23.html
|
||||
var tiddly embed.FS
|
||||
const landingPage = `
|
||||
<h1>Hello{{if .User}} {{.User}}{{end}}! Welcome to widdler!</h1>
|
||||
|
||||
<p>To create a new TiddlyWiki html file, simply append an html file name to the URL in the address bar!</p>
|
||||
|
||||
<h3>For example:</h3>
|
||||
|
||||
<a href="{{.URL}}">{{.URL}}</a>
|
||||
|
||||
<p>This will create a new wiki called "<b>wiki.html</b>"</p>
|
||||
|
||||
<p>After creating a wiki, this message will be replaced by a list of your wiki files.</p>
|
||||
`
|
||||
|
||||
var (
|
||||
davDir string
|
||||
listen string
|
||||
passPath string
|
||||
users map[string]string
|
||||
twFile = "empty-5.2.1.html"
|
||||
|
||||
//go:embed empty-5.2.1.html
|
||||
tiddly embed.FS
|
||||
templ *template.Template
|
||||
)
|
||||
|
||||
type userHandler struct {
|
||||
mu sync.Mutex
|
||||
dav *webdav.Handler
|
||||
fs http.Handler
|
||||
name string
|
||||
}
|
||||
|
||||
type userHandlers struct {
|
||||
list []userHandler
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (u *userHandlers) find(name string) *userHandler {
|
||||
for i := range u.list {
|
||||
if u.list[i].name == name {
|
||||
return &u.list[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
auth string
|
||||
davDir string
|
||||
fullListen string
|
||||
genHtpass bool
|
||||
handlers userHandlers
|
||||
listen string
|
||||
passPath string
|
||||
tlsCert string
|
||||
tlsKey string
|
||||
users map[string]string
|
||||
version bool
|
||||
build string
|
||||
)
|
||||
|
||||
var pledges = "stdio wpath rpath cpath tty inet dns unveil"
|
||||
|
||||
func init() {
|
||||
users = make(map[string]string)
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
|
@ -40,36 +98,26 @@ func init() {
|
|||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
flag.StringVar(&davDir, "davdir", dir, "Directory to serve over WebDAV.")
|
||||
flag.StringVar(&listen, "http", "127.0.0.1:8080", "Listen on")
|
||||
flag.StringVar(&davDir, "wikis", dir, "Directory of TiddlyWikis to serve over WebDAV.")
|
||||
flag.StringVar(&listen, "http", "localhost:8080", "Listen on")
|
||||
flag.StringVar(&tlsCert, "tlscert", "", "TLS certificate.")
|
||||
flag.StringVar(&tlsKey, "tlskey", "", "TLS key.")
|
||||
flag.StringVar(&passPath, "htpass", fmt.Sprintf("%s/.htpasswd", dir), "Path to .htpasswd file..")
|
||||
flag.StringVar(&auth, "auth", "basic", "Enable HTTP Basic Authentication.")
|
||||
flag.BoolVar(&genHtpass, "gen", false, "Generate a .htpasswd file or add a new entry to an existing file.")
|
||||
flag.BoolVar(&version, "v", false, "Show version and exit.")
|
||||
flag.Parse()
|
||||
|
||||
// These are OpenBSD specific protections used to prevent unnecessary file access.
|
||||
_ = protect.Unveil(passPath, "r")
|
||||
_ = protect.Unveil(passPath, "rwc")
|
||||
_ = protect.Unveil(davDir, "rwc")
|
||||
_ = protect.Unveil("/etc/ssl/cert.pem", "r")
|
||||
_ = protect.Unveil("/etc/resolv.conf", "r")
|
||||
_ = protect.Pledge("stdio wpath rpath cpath inet dns")
|
||||
_ = protect.Pledge(pledges)
|
||||
|
||||
p, err := os.Open(passPath)
|
||||
templ, err = template.New("landing").Parse(landingPage)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
ht := csv.NewReader(p)
|
||||
ht.Comma = ':'
|
||||
ht.Comment = '#'
|
||||
ht.TrimLeadingSpace = true
|
||||
|
||||
entries, err := ht.ReadAll()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, parts := range entries {
|
||||
users[parts[0]] = parts[1]
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +152,7 @@ func createEmpty(path string) error {
|
|||
if os.IsNotExist(fErr) {
|
||||
log.Printf("creating %q\n", path)
|
||||
twData, _ := tiddly.ReadFile(twFile)
|
||||
wErr := ioutil.WriteFile(path, []byte(twData), 0600)
|
||||
wErr := ioutil.WriteFile(path, twData, 0600)
|
||||
if wErr != nil {
|
||||
return wErr
|
||||
}
|
||||
|
@ -112,43 +160,190 @@ func createEmpty(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func prompt(prompt string, secure bool) (string, error) {
|
||||
var input string
|
||||
fmt.Print(prompt)
|
||||
|
||||
if secure {
|
||||
b, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
input = string(b)
|
||||
} else {
|
||||
_, err := fmt.Scanln(&input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
wdav := &webdav.Handler{
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
FileSystem: webdav.Dir(davDir),
|
||||
if version {
|
||||
fmt.Println(build)
|
||||
os.Exit(0)
|
||||
}
|
||||
if genHtpass {
|
||||
user, err := prompt("Username: ", false)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
pass, err := prompt("Password: ", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(pass), 11)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filepath.Clean(passPath), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if _, err := f.WriteString(fmt.Sprintf("%s:%s\n", user, hash)); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Added %q to %q\n", user, passPath)
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
pledges, _ = protect.ReducePledges(pledges, "tty")
|
||||
|
||||
// drop to only read on passPath
|
||||
_ = protect.Unveil(passPath, "r")
|
||||
pledges, _ = protect.ReducePledges(pledges, "unveil")
|
||||
|
||||
_, fErr := os.Stat(passPath)
|
||||
if os.IsNotExist(fErr) {
|
||||
if auth == "basic" || auth == "header" {
|
||||
fmt.Println("No .htpasswd file found!")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
p, err := os.Open(filepath.Clean(passPath))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ht := csv.NewReader(p)
|
||||
ht.Comma = ':'
|
||||
ht.Comment = '#'
|
||||
ht.TrimLeadingSpace = true
|
||||
|
||||
entries, err := ht.ReadAll()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = p.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, parts := range entries {
|
||||
users[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
idxHandler := http.FileServer(http.Dir(davDir))
|
||||
if auth == "basic" || auth == "header" {
|
||||
for u := range users {
|
||||
uPath := path.Join(davDir, u)
|
||||
handlers.list = append(handlers.list, userHandler{
|
||||
name: u,
|
||||
dav: &webdav.Handler{
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
FileSystem: webdav.Dir(uPath),
|
||||
},
|
||||
fs: http.FileServer(http.Dir(uPath)),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
handlers.list = append(handlers.list, userHandler{
|
||||
name: "",
|
||||
dav: &webdav.Handler{
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
FileSystem: webdav.Dir(davDir),
|
||||
},
|
||||
fs: http.FileServer(http.Dir(davDir)),
|
||||
})
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", logger(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass := "", ""
|
||||
var ok bool
|
||||
|
||||
if strings.Contains(r.URL.Path, ".htpasswd") {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !(ok && authenticate(user, pass)) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="davfs"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
// Prevent directory traversal
|
||||
if strings.Contains(r.URL.Path, "..") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
//wdav.Prefix = user
|
||||
|
||||
fp := path.Join(davDir, r.URL.Path)
|
||||
/*
|
||||
fp := path.Join(davDir, user, r.URL.Path)
|
||||
_, dErr := os.Stat(user)
|
||||
if os.IsNotExist(dErr) {
|
||||
mErr := os.Mkdir(path.Join(davDir, user), 0700)
|
||||
if mErr != nil {
|
||||
http.Error(w, mErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
if auth == "basic" {
|
||||
user, pass, ok = r.BasicAuth()
|
||||
if !(ok && authenticate(user, pass)) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="widdler"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
} else if auth == "header" {
|
||||
var prefix = "Auth"
|
||||
for name, values := range r.Header {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
user = strings.TrimLeft(name, prefix)
|
||||
pass = values[0]
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if !(ok && authenticate(user, pass)) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="widdler"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
handlers.mu.RLock()
|
||||
handler := handlers.find(user)
|
||||
handlers.mu.RUnlock()
|
||||
|
||||
if handler == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
handler.mu.Lock()
|
||||
|
||||
defer handler.mu.Unlock()
|
||||
|
||||
userPath := path.Join(davDir, user)
|
||||
fullPath := path.Join(davDir, user, r.URL.Path)
|
||||
|
||||
_, dErr := os.Stat(userPath)
|
||||
if os.IsNotExist(dErr) {
|
||||
mErr := os.Mkdir(userPath, 0700)
|
||||
if mErr != nil {
|
||||
http.Error(w, mErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isHTML, err := regexp.Match(`\.html$`, []byte(r.URL.Path))
|
||||
if err != nil {
|
||||
|
@ -158,17 +353,37 @@ func main() {
|
|||
|
||||
if isHTML {
|
||||
// HTML files will be created or sent back
|
||||
err := createEmpty(fp)
|
||||
err := createEmpty(fullPath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
wdav.ServeHTTP(w, r)
|
||||
handler.dav.ServeHTTP(w, r)
|
||||
} else {
|
||||
// Everything else is browsable
|
||||
idxHandler.ServeHTTP(w, r)
|
||||
return
|
||||
entries, err := os.ReadDir(userPath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(entries) > 0 {
|
||||
handler.fs.ServeHTTP(w, r)
|
||||
} else {
|
||||
l := Landing{
|
||||
URL: fmt.Sprintf("%s/wiki.html", fullListen),
|
||||
}
|
||||
if user != "" {
|
||||
l.User = user
|
||||
}
|
||||
err = templ.ExecuteTemplate(w, "landing", l)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
|
@ -178,9 +393,24 @@ func main() {
|
|||
|
||||
lis, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Printf("Listening for HTTP on '%s'", listen)
|
||||
log.Panic(s.Serve(lis))
|
||||
if tlsCert != "" && tlsKey != "" {
|
||||
fullListen = fmt.Sprintf("https://%s", listen)
|
||||
|
||||
s.TLSConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
}
|
||||
|
||||
log.Printf("Listening for HTTPS on 'https://%s'", listen)
|
||||
log.Fatalln(s.ServeTLS(lis, tlsCert, tlsKey))
|
||||
} else {
|
||||
fullListen = fmt.Sprintf("http://%s", listen)
|
||||
|
||||
log.Printf("Listening for HTTP on 'http://%s'", listen)
|
||||
log.Fatalln(s.Serve(lis))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue