Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
7218fbc27f | |||
5148202a0f | |||
1edba52d32 | |||
9c42144b45 | |||
58197af85d | |||
4e098ec665 | |||
5567de7de1 | |||
63e03f6459 | |||
90837f1299 | |||
b50a80a33d | |||
92212c97eb | |||
3f16c3e799 | |||
34c31448bb | |||
514c6208e2 | |||
88b989bfbb | |||
7915091b96 | |||
f22fa1ee1d | |||
34890ff93f | |||
fea3e7104d | |||
2dc523271c | |||
4048390bfb | |||
6106e817cf | |||
30ffa6805b | |||
17781c5445 | |||
3b15052330 | |||
271c27f4ad | |||
532e8d9da6 | |||
72d3104150 | |||
5bd6120ae5 | |||
d919d00cc0 |
6
Makefile
6
Makefile
@ -5,7 +5,7 @@ SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
|
|||||||
SYSDDIR=${SYSDDIR_:/%=%}
|
SYSDDIR=${SYSDDIR_:/%=%}
|
||||||
DESTDIR=/
|
DESTDIR=/
|
||||||
|
|
||||||
VERSION=23.31.0
|
VERSION=23.34.0
|
||||||
|
|
||||||
FLAGS=-trimpath -mod=readonly -modcacherw
|
FLAGS=-trimpath -mod=readonly -modcacherw
|
||||||
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
||||||
@ -33,8 +33,6 @@ install:
|
|||||||
install -Dm 0644 init/systemd/${TARGET}-clean.timer ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
install -Dm 0644 init/systemd/${TARGET}-clean.timer ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
||||||
install -Dm 0644 init/systemd/${TARGET}-clean.service ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
install -Dm 0644 init/systemd/${TARGET}-clean.service ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
||||||
|
|
||||||
install -Dm 0644 build/dwelling-upload.conf ${DESTDIR}usr/lib/sysusers.d/dwelling-upload.conf
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm ${DESTDIR}usr/bin/${TARGET}
|
rm ${DESTDIR}usr/bin/${TARGET}
|
||||||
rm ${DESTDIR}usr/bin/${TARGET}-clean
|
rm ${DESTDIR}usr/bin/${TARGET}-clean
|
||||||
@ -45,8 +43,6 @@ uninstall:
|
|||||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
||||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
||||||
|
|
||||||
rm ${DESTDIR}usr/lib/sysusers.d/dwelling-upload.conf
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f web/*.jade.go
|
rm -f web/*.jade.go
|
||||||
go clean
|
go clean
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
||||||
pkgname=dwelling-upload
|
pkgname=dwelling-upload
|
||||||
pkgver=23.31.0
|
pkgver=23.34.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Arav's Dwelling / Upload"
|
pkgdesc="Arav's Dwelling / Upload"
|
||||||
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# sysusers.d
|
|
||||||
g dwupload - -
|
|
||||||
u dwupload - -
|
|
||||||
m dwupload dwupload
|
|
@ -10,10 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
uploadDir *string = flag.String("dir", "/srv/upload", "path to a directory where uploaded files are stored")
|
uploadDir = flag.String("dir", "/srv/upload", "path to a directory where uploaded files are stored")
|
||||||
expiry *int64 = flag.Int64("expiry", 36, "keep files for this much hours")
|
expiry = flag.Duration("expiry", 36*time.Hour, "keep files for this much hours")
|
||||||
|
|
||||||
showVersion *bool = flag.Bool("v", false, "show version")
|
showVersion = flag.Bool("v", false, "show version")
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string
|
var version string
|
||||||
@ -39,7 +39,7 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().UTC().Sub(file.ModTime().UTC()) >= time.Duration(*expiry)*time.Hour {
|
if time.Now().UTC().Sub(file.ModTime().UTC()) >= *expiry {
|
||||||
if err := os.Remove(path.Join(*uploadDir, entry.Name())); err != nil {
|
if err := os.Remove(path.Join(*uploadDir, entry.Name())); err != nil {
|
||||||
log.Printf("failed to remove a file %s: %s", entry.Name(), err)
|
log.Printf("failed to remove a file %s: %s", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dwelling-upload/internal/http"
|
duihttp "dwelling-upload/internal/http"
|
||||||
"dwelling-upload/pkg/utils"
|
"dwelling-upload/pkg/utils"
|
||||||
"dwelling-upload/pkg/watcher"
|
"dwelling-upload/pkg/watcher"
|
||||||
"dwelling-upload/web"
|
"dwelling-upload/web"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
nethttp "net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.arav.su/Arav/httpr"
|
"git.arav.su/Arav/httpr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
listenAddress *string = flag.String("listen", "/var/run/dwelling-upload/sock", "listen address (ip:port|unix_path)")
|
listenAddress = flag.String("listen", "/var/run/dwelling-upload/sock", "listen address (ip:port|unix_path)")
|
||||||
uploadDir *string = flag.String("dir", "/srv/upload", "directory where uploaded files are stored")
|
uploadDir = flag.String("dir", "/srv/upload", "directory where uploaded files are stored")
|
||||||
expiry *int = flag.Int("expiry", 36, "keep files for this much hours")
|
expiry = flag.Int("expiry", 36, "keep files for this much hours")
|
||||||
storageSize *int64 = flag.Int64("storage", 102400, "storage size in MiB for uploads")
|
storageSize = flag.Int64("storage", 102400, "storage size in MiB for uploads")
|
||||||
fileSize *int64 = flag.Int64("file", 128, "max. size in MiB for files")
|
fileSize = flag.Int64("file", 128, "max. size in MiB for files")
|
||||||
|
|
||||||
showVersion *bool = flag.Bool("v", false, "show version")
|
showVersion *bool = flag.Bool("v", false, "show version")
|
||||||
)
|
)
|
||||||
@ -40,36 +38,6 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var network string
|
|
||||||
if !strings.ContainsRune(*listenAddress, ':') {
|
|
||||||
network = "unix"
|
|
||||||
defer os.Remove(*listenAddress)
|
|
||||||
} else {
|
|
||||||
ap, err := netip.ParseAddrPort(*listenAddress)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ap.Addr().Is4() {
|
|
||||||
network = "tcp4"
|
|
||||||
} else {
|
|
||||||
network = "tcp6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watcha, err := watcher.NewInotifyWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
defer watcha.Close()
|
|
||||||
|
|
||||||
if err := watcha.AddWatch(*uploadDir, watcher.CrDelMask); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadDirNotify := make(chan uint32)
|
|
||||||
go watcha.WatchForMask(uploadDirNotify, watcher.CrDelMask)
|
|
||||||
|
|
||||||
hashSalt, err := os.ReadFile(path.Join(os.Getenv("CREDENTIALS_DIRECTORY"), "salt"))
|
hashSalt, err := os.ReadFile(path.Join(os.Getenv("CREDENTIALS_DIRECTORY"), "salt"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("failed to read hash salt file:", err)
|
log.Fatalln("failed to read hash salt file:", err)
|
||||||
@ -90,39 +58,52 @@ func main() {
|
|||||||
log.Fatalf("failed to get initial size of %s: %s", *uploadDir, err)
|
log.Fatalf("failed to get initial size of %s: %s", *uploadDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hand := http.NewUploadHandlers(logFile, *uploadDir, &uploadDirSize, string(hashSalt),
|
hand := duihttp.NewUploadHandlers(logFile, *uploadDir, &uploadDirSize, string(hashSalt),
|
||||||
*expiry, *storageSize, *fileSize)
|
*expiry, *storageSize, *fileSize)
|
||||||
|
|
||||||
r := httpr.New()
|
r := httpr.New()
|
||||||
|
|
||||||
r.NotFoundHandler = func(w nethttp.ResponseWriter, r *nethttp.Request) {
|
r.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, r, nethttp.StatusNotFound, "")
|
duihttp.Error(w, r, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Handler(nethttp.MethodGet, "/", hand.Index)
|
r.Handler(http.MethodGet, "/", hand.Index)
|
||||||
r.Handler(nethttp.MethodPost, "/", hand.Upload)
|
r.Handler(http.MethodPost, "/", hand.Upload)
|
||||||
r.Handler(nethttp.MethodGet, "/:hash/:name", hand.Download)
|
r.Handler(http.MethodGet, "/:hash/:name", hand.Download)
|
||||||
r.Handler(nethttp.MethodPost, "/delete", hand.Delete)
|
r.Handler(http.MethodPost, "/delete", hand.Delete)
|
||||||
r.Handler(nethttp.MethodDelete, "/:hash", hand.Delete)
|
r.Handler(http.MethodDelete, "/:hash", hand.Delete)
|
||||||
|
|
||||||
r.ServeStatic("/assets/*filepath", web.Assets())
|
r.ServeStatic("/assets/*filepath", web.Assets())
|
||||||
r.Handler(nethttp.MethodGet, "/robots.txt", http.RobotsTxt)
|
r.Handler(http.MethodGet, "/robots.txt", duihttp.RobotsTxt)
|
||||||
r.Handler(nethttp.MethodGet, "/favicon.svg", http.Favicon)
|
r.Handler(http.MethodGet, "/favicon.svg", duihttp.Favicon)
|
||||||
|
|
||||||
srv := http.NewHttpServer(r)
|
srv := duihttp.NewHttpServer(r)
|
||||||
|
|
||||||
|
if err := srv.Start(*listenAddress); err != nil {
|
||||||
|
log.Fatalln("failed to start a server:", err)
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := srv.Stop(); err != nil {
|
if err := srv.Stop(); err != nil {
|
||||||
log.Fatalln("failed to properly shutdown a server:", err)
|
log.Fatalln("failed to properly shutdown a server:", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := srv.Start(network, *listenAddress); err != nil {
|
|
||||||
log.Fatalln("failed to start a server:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
doneSignal := make(chan os.Signal, 1)
|
doneSignal := make(chan os.Signal, 1)
|
||||||
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
watcha, err := watcher.NewInotifyWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
defer watcha.Close()
|
||||||
|
|
||||||
|
if err := watcha.AddWatch(*uploadDir, watcher.CrDelMask); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadDirNotify := make(chan uint32)
|
||||||
|
go watcha.WatchForMask(uploadDirNotify, watcher.CrDelMask)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -3,14 +3,15 @@ server {
|
|||||||
listen 8094; # Tor I2P
|
listen 8094; # Tor I2P
|
||||||
listen [300:a98d:d6d0:8a08::c]:80; # Yggdrasil
|
listen [300:a98d:d6d0:8a08::c]:80; # Yggdrasil
|
||||||
|
|
||||||
server_name upload.arav.su upload.arav.i2p;
|
server_name upload.arav.su upload.arav.i2p 4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion;
|
||||||
|
|
||||||
access_log /var/log/nginx/dwelling/upload.log main if=$nolog;
|
access_log /var/log/nginx/dwelling/upload.log main if=$nolog;
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/arav.su/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/arav.su/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/arav.su/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/arav.su/privkey.pem;
|
||||||
|
|
||||||
|
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none'; frame-ancestors 'none'; font-src 'self'; form-action 'self'";
|
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none'; frame-ancestors 'none'; font-src 'self'; form-action 'self'";
|
||||||
add_header X-Frame-Options "DENY";
|
add_header X-Frame-Options "DENY";
|
||||||
add_header X-Content-Type-Options "nosniff";
|
add_header X-Content-Type-Options "nosniff";
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
2
go.mod
2
go.mod
@ -4,4 +4,4 @@ go 1.17
|
|||||||
|
|
||||||
require github.com/pkg/errors v0.9.1
|
require github.com/pkg/errors v0.9.1
|
||||||
|
|
||||||
require git.arav.su/Arav/httpr v0.2.1
|
require git.arav.su/Arav/httpr v0.3.1
|
||||||
|
4
go.sum
4
go.sum
@ -1,4 +1,4 @@
|
|||||||
git.arav.su/Arav/httpr v0.2.1 h1:wtago8iYOqeoyOvio1uhMItXaTHXiBVfsshtYWSURhw=
|
git.arav.su/Arav/httpr v0.3.1 h1:8ba90SJ4XYUWfIlC3V0Zuw3+CcOb9IYVkOZ/2mB9JO0=
|
||||||
git.arav.su/Arav/httpr v0.2.1/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
git.arav.su/Arav/httpr v0.3.1/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -3,9 +3,8 @@ Description=dwelling-upload-clean
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
User=dwupload
|
DynamicUser=yes
|
||||||
Group=dwupload
|
ExecStart=/usr/bin/dwelling-upload-clean -dir /srv/upload -expiry 36h
|
||||||
ExecStart=/usr/bin/dwelling-upload-clean -dir /srv/upload -expiry 36
|
|
||||||
|
|
||||||
ReadOnlyPaths=/
|
ReadOnlyPaths=/
|
||||||
# Set here path to directory where uploads are stored.
|
# Set here path to directory where uploads are stored.
|
||||||
@ -20,18 +19,33 @@ LockPersonality=true
|
|||||||
MemoryDenyWriteExecute=true
|
MemoryDenyWriteExecute=true
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
PrivateDevices=true
|
PrivateDevices=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateUsers=true
|
||||||
|
ProcSubset=pid
|
||||||
ProtectClock=true
|
ProtectClock=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
ProtectKernelLogs=true
|
ProtectKernelLogs=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
ProtectKernelTunables=true
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
RestrictAddressFamilies=
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
RestrictRealtime=true
|
RestrictRealtime=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
SystemCallArchitectures=native
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -5,10 +5,9 @@ After=network.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
User=dwupload
|
DynamicUser=yes
|
||||||
Group=dwupload
|
ExecStart=/usr/bin/dwelling-upload -listen /var/run/dwelling-upload/sock \
|
||||||
ExecStart=/usr/bin/dwelling-upload -listen /var/run/dwelling-upload/sock -dir /srv/upload \
|
-dir /srv/upload -expiry 36 -storage 102400 -file 128
|
||||||
-expiry 36 -storage 102400 -file 128
|
|
||||||
|
|
||||||
ReadOnlyPaths=/
|
ReadOnlyPaths=/
|
||||||
# Set here path to directory where uploads are stored.
|
# Set here path to directory where uploads are stored.
|
||||||
@ -29,18 +28,33 @@ LockPersonality=true
|
|||||||
MemoryDenyWriteExecute=true
|
MemoryDenyWriteExecute=true
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
PrivateDevices=true
|
PrivateDevices=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateUsers=true
|
||||||
|
ProcSubset=pid
|
||||||
ProtectClock=true
|
ProtectClock=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
ProtectKernelLogs=true
|
ProtectKernelLogs=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
ProtectKernelTunables=true
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
RestrictRealtime=true
|
RestrictRealtime=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
SystemCallArchitectures=native
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -62,14 +62,14 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if err := r.ParseMultipartForm(fMaxSizeBytes); err != nil {
|
if err := r.ParseMultipartForm(fMaxSizeBytes); err != nil {
|
||||||
log.Println("failed to parse upload form:", err)
|
log.Println("failed to parse upload form:", err)
|
||||||
Error(w, r, http.StatusExpectationFailed, "Failed to parse upload form.")
|
Error(w, r, "Failed to parse upload form.", http.StatusExpectationFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, fHandler, err := r.FormFile("file")
|
f, fHandler, err := r.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("failed to open incoming file:", err)
|
log.Println("failed to open incoming file:", err)
|
||||||
Error(w, r, http.StatusInternalServerError, "Error reading an incoming file.")
|
Error(w, r, "Error reading an incoming file.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(fHandler.Filename)
|
defer os.Remove(fHandler.Filename)
|
||||||
@ -79,19 +79,20 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if leftSpace < fHandler.Size {
|
if leftSpace < fHandler.Size {
|
||||||
log.Println("not enough space left in storage, only", leftSpace>>20, "MiB left")
|
log.Println("not enough space left in storage, only", leftSpace>>20, "MiB left")
|
||||||
Error(w, r, http.StatusInternalServerError, "Not enough space left, sorry.")
|
Error(w, r, "Not enough space left, sorry.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s256 := sha256.New()
|
s256 := sha256.New()
|
||||||
if _, err := io.Copy(s256, f); err != nil {
|
if _, err := io.Copy(s256, f); err != nil {
|
||||||
log.Println("failed to compute a SHA-256 hash:", err)
|
log.Println("failed to compute a SHA-256 hash:", err)
|
||||||
Error(w, r, http.StatusInternalServerError, "A hash for the file cannot be computed.")
|
Error(w, r, "A hash for the file cannot be computed.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fHash := hex.EncodeToString(s256.Sum(nil))
|
fHash := hex.EncodeToString(s256.Sum(nil))
|
||||||
s256.Write([]byte(h.hashSalt))
|
s256.Write([]byte(h.hashSalt))
|
||||||
|
s256.Write([]byte(time.Now().String()))
|
||||||
fSaltedHash := base64.RawURLEncoding.EncodeToString(s256.Sum(nil))
|
fSaltedHash := base64.RawURLEncoding.EncodeToString(s256.Sum(nil))
|
||||||
|
|
||||||
f.Seek(0, io.SeekStart)
|
f.Seek(0, io.SeekStart)
|
||||||
@ -103,7 +104,7 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
fDst, err := os.Create(fPath)
|
fDst, err := os.Create(fPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("failed to open file for writing", err)
|
log.Println("failed to open file for writing", err)
|
||||||
Error(w, r, http.StatusInternalServerError, "File cannot be written.")
|
Error(w, r, "File cannot be written.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer fDst.Close()
|
defer fDst.Close()
|
||||||
@ -118,14 +119,18 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if _, err = io.Copy(fDst, f); err != nil {
|
if _, err = io.Copy(fDst, f); err != nil {
|
||||||
log.Println("failed to copy uploaded file to destination:", err)
|
log.Println("failed to copy uploaded file to destination:", err)
|
||||||
Error(w, r, http.StatusInternalServerError, "Failed to copy uploaded file to the storage.")
|
Error(w, r, "Failed to copy uploaded file to the storage.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
typ, _ := utils.NetworkType(r.Host)
|
typ, _ := utils.NetworkType(r.Host)
|
||||||
|
ip := r.Header.Get("X-Real-IP")
|
||||||
|
if typ != "www" && typ != "ygg" {
|
||||||
|
ip = ""
|
||||||
|
}
|
||||||
|
|
||||||
h.logFile.Printf("| up | %s | %s | %s | SHA256 %s | %s | %d | %s",
|
h.logFile.Printf("| up | %s | %s | %s | SHA256 %s | %s | %d | %s",
|
||||||
r.Header.Get("X-Real-IP"), typ, fHandler.Filename, fHash, fSaltedHash, fHandler.Size, r.UserAgent())
|
ip, typ, fHandler.Filename, fHash, fSaltedHash, fHandler.Size, r.UserAgent())
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
} else {
|
} else {
|
||||||
@ -153,7 +158,7 @@ func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
stat, err := os.Stat(path)
|
stat, err := os.Stat(path)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
Error(w, r, http.StatusNotFound, "")
|
Error(w, r, "", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,15 +169,19 @@ func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
fd, err := os.Open(path)
|
fd, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("failed to open file to read:", err)
|
log.Println("failed to open file to read:", err)
|
||||||
Error(w, r, http.StatusInternalServerError, "Failed to open file to read.")
|
Error(w, r, "Failed to open file to read.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
netTyp, _ := utils.NetworkType(r.Host)
|
typ, _ := utils.NetworkType(r.Host)
|
||||||
|
ip := r.Header.Get("X-Real-IP")
|
||||||
|
if typ != "www" && typ != "ygg" {
|
||||||
|
ip = ""
|
||||||
|
}
|
||||||
|
|
||||||
h.logFile.Printf("| dw | %s | %s | %s | %s | %s",
|
h.logFile.Printf("| dw | %s | %s | %s | %s | %s",
|
||||||
r.Header.Get("X-Real-IP"), netTyp, name, saltedHash, r.UserAgent())
|
ip, typ, name, saltedHash, r.UserAgent())
|
||||||
|
|
||||||
http.ServeContent(w, r, path, stat.ModTime(), fd)
|
http.ServeContent(w, r, path, stat.ModTime(), fd)
|
||||||
}
|
}
|
||||||
@ -189,20 +198,24 @@ func (h *UploadHandlers) Delete(w http.ResponseWriter, r *http.Request) {
|
|||||||
path := path.Join(h.uploadDir, saltedHash)
|
path := path.Join(h.uploadDir, saltedHash)
|
||||||
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
Error(w, r, http.StatusNotFound, "")
|
Error(w, r, "", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Remove(path); err != nil {
|
if err := os.Remove(path); err != nil {
|
||||||
log.Println("failed to remove a file:", err)
|
log.Println("failed to remove a file:", err)
|
||||||
Error(w, r, http.StatusInternalServerError, "Failed to remove a file.")
|
Error(w, r, "Failed to remove a file.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
netTyp, _ := utils.NetworkType(r.Host)
|
typ, _ := utils.NetworkType(r.Host)
|
||||||
|
ip := r.Header.Get("X-Real-IP")
|
||||||
|
if typ != "www" && typ != "ygg" {
|
||||||
|
ip = ""
|
||||||
|
}
|
||||||
|
|
||||||
h.logFile.Printf("| dt | %s | %s | %s | %s",
|
h.logFile.Printf("| dt | %s | %s | %s | %s",
|
||||||
r.Header.Get("X-Real-IP"), netTyp, saltedHash, r.UserAgent())
|
ip, typ, saltedHash, r.UserAgent())
|
||||||
|
|
||||||
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
|
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
|
||||||
fmt.Fprintln(w, "File was successfully deleted.")
|
fmt.Fprintln(w, "File was successfully deleted.")
|
||||||
@ -211,7 +224,7 @@ func (h *UploadHandlers) Delete(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(w http.ResponseWriter, r *http.Request, code int, reason string) {
|
func Error(w http.ResponseWriter, r *http.Request, reason string, code int) {
|
||||||
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
|
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
|
||||||
http.Error(w, reason, code)
|
http.Error(w, reason, code)
|
||||||
return
|
return
|
||||||
|
@ -5,12 +5,15 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpServer struct {
|
type HttpServer struct {
|
||||||
s http.Server
|
s http.Server
|
||||||
|
addr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpServer(r http.Handler) *HttpServer {
|
func NewHttpServer(r http.Handler) *HttpServer {
|
||||||
@ -20,7 +23,23 @@ func NewHttpServer(r http.Handler) *HttpServer {
|
|||||||
Handler: r}}
|
Handler: r}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HttpServer) Start(network, address string) error {
|
func (s *HttpServer) Start(address string) error {
|
||||||
|
var network string
|
||||||
|
if !strings.ContainsRune(address, ':') {
|
||||||
|
network = "unix"
|
||||||
|
} else {
|
||||||
|
ap, err := netip.ParseAddrPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ap.Addr().Is4() {
|
||||||
|
network = "tcp4"
|
||||||
|
} else if ap.Addr().Is6() {
|
||||||
|
network = "tcp6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen(network, address)
|
listener, err := net.Listen(network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -30,6 +49,8 @@ func (s *HttpServer) Start(network, address string) error {
|
|||||||
os.Chmod(address, 0777)
|
os.Chmod(address, 0777)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.addr = listener.Addr()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err = s.s.Serve(listener); err != nil && err != http.ErrServerClosed {
|
if err = s.s.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
@ -43,6 +64,10 @@ func (s *HttpServer) Stop() error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
if s.addr.Network() == "unix" {
|
||||||
|
defer os.Remove(s.addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.s.Shutdown(ctx); err != nil {
|
if err := s.s.Shutdown(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ func NetworkType(host string) (string, string) {
|
|||||||
return "i2p", "http"
|
return "i2p", "http"
|
||||||
} else if strings.Contains(host, "onion") {
|
} else if strings.Contains(host, "onion") {
|
||||||
return "tor", "http"
|
return "tor", "http"
|
||||||
|
} else if strings.Contains(host, "[300:") {
|
||||||
|
return "ygg", "http"
|
||||||
} else {
|
} else {
|
||||||
return "www", "https"
|
return "www", "https"
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@
|
|||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
color: var(--background-color); }
|
color: var(--background-color); }
|
||||||
|
|
||||||
|
.center { text-align: center; }
|
||||||
|
|
||||||
a,
|
a,
|
||||||
button {
|
button {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
@ -50,8 +52,8 @@ input[type="file"] {
|
|||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font: inherit; }
|
font: inherit; }
|
||||||
|
|
||||||
input[type="file"]::file-selector-button,
|
button,
|
||||||
button {
|
input[type="file"]::file-selector-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
@ -91,8 +93,6 @@ h2 {
|
|||||||
|
|
||||||
small { font-size: .8rem; }
|
small { font-size: .8rem; }
|
||||||
|
|
||||||
.center { text-align: center; }
|
|
||||||
|
|
||||||
html { margin-left: calc(100vw - 100%); }
|
html { margin-left: calc(100vw - 100%); }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -109,24 +109,22 @@ header {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between; }
|
justify-content: space-between; }
|
||||||
|
|
||||||
#logo {
|
header svg { width: 360px; }
|
||||||
display: block;
|
|
||||||
width: 360px; }
|
|
||||||
|
|
||||||
#logo text { fill: var(--text-color); }
|
header svg text { fill: var(--text-color); }
|
||||||
|
|
||||||
#logo .logo {
|
header svg text:first-child {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-variant-caps: small-caps;
|
font-variant-caps: small-caps;
|
||||||
font-weight: bold; }
|
font-weight: bold; }
|
||||||
|
|
||||||
|
header svg text:last-child { font-size: .88rem; }
|
||||||
|
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
#logo .logo { font-size: 2.082rem; } }
|
header svg text:first-child { font-size: 2.082rem; } }
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
@-moz-document url-prefix() {
|
||||||
#logo .logo { font-size: 2rem; } }
|
header svg text:first-child { font-size: 2rem; } }
|
||||||
|
|
||||||
#logo .under { font-size: .88rem; }
|
|
||||||
|
|
||||||
nav { margin-top: .5rem; }
|
nav { margin-top: .5rem; }
|
||||||
|
|
||||||
@ -138,6 +136,14 @@ nav h1 {
|
|||||||
|
|
||||||
section { margin-top: 1rem; }
|
section { margin-top: 1rem; }
|
||||||
|
|
||||||
|
#error {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
line-height: 5rem;
|
||||||
|
text-align: center;
|
||||||
|
margin: 6rem 0; }
|
||||||
|
|
||||||
|
#error h1 { font-size: 8rem; }
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -146,7 +152,7 @@ footer {
|
|||||||
@media screen and (max-width: 640px) {
|
@media screen and (max-width: 640px) {
|
||||||
header { display: block; }
|
header { display: block; }
|
||||||
|
|
||||||
#logo {
|
header svg {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /assets/
|
Allow: /$
|
||||||
|
Disallow: /
|
@ -12,9 +12,9 @@ html(lang="en")
|
|||||||
block head
|
block head
|
||||||
body
|
body
|
||||||
header
|
header
|
||||||
svg#logo(viewBox="0 -25 216 40")
|
svg(viewBox="0 -25 216 40")
|
||||||
text.logo Arav's dwelling
|
text Arav's dwelling
|
||||||
text.under(y="11") Welcome to my sacred place, wanderer
|
text(y="11") Welcome to my sacred place, wanderer
|
||||||
nav
|
nav
|
||||||
a(href=mainSite title="Arav's dwelling") Back to main website
|
a(href=mainSite title="Arav's dwelling") Back to main website
|
||||||
block header
|
block header
|
||||||
|
@ -1,20 +1,10 @@
|
|||||||
extends base.jade
|
extends base.jade
|
||||||
|
|
||||||
block head
|
|
||||||
:go:func ErrorXXX(mainSite string, code int, errorMsg string)
|
|
||||||
style(type="text/css").
|
|
||||||
#error {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
line-height: 5rem;
|
|
||||||
text-align: center;
|
|
||||||
margin: 6rem 0; }
|
|
||||||
|
|
||||||
#error h1 { font-size: 8rem; }
|
|
||||||
|
|
||||||
block header
|
block header
|
||||||
h1 Еггог
|
h1 Еггог
|
||||||
|
|
||||||
block body
|
block body
|
||||||
|
:go:func ErrorXXX(mainSite string, code int, errorMsg string)
|
||||||
section#error
|
section#error
|
||||||
h1 #{code}
|
h1 #{code}
|
||||||
| #{http.StatusText(code)}
|
| #{http.StatusText(code)}
|
||||||
|
@ -5,26 +5,24 @@ block header
|
|||||||
|
|
||||||
block body
|
block body
|
||||||
:go:func Index(mainSite string, keepForHours int, fileMaxSize, storageAvailableStr string)
|
:go:func Index(mainSite string, keepForHours int, fileMaxSize, storageAvailableStr string)
|
||||||
section#rules
|
section
|
||||||
h2 Rules
|
h2 Rules
|
||||||
p Maximum file size is #[b #{fileMaxSize}] and it will be kept for #[b #{keepForHours}] hours.
|
p Maximum file size is #[b #{fileMaxSize}] and it will be kept for #[b #{keepForHours}] hours.
|
||||||
p Content you upload should comply with Russian Federation's law. Generally speaking, anything illegal, like CP, extremist literature, and so on is forbidden.
|
p Content you upload should comply with Russian Federation's law. Generally speaking, anything illegal, like CP, extremist literature, and so on is forbidden.
|
||||||
section#upload.center
|
section.center
|
||||||
h2 Upload
|
h2 Upload
|
||||||
form(action="/" method="POST" enctype="multipart/form-data")
|
form(action="/" method="POST" enctype="multipart/form-data")
|
||||||
input(type="file" name="file" multiple=false)
|
input(type="file" name="file" multiple=false)
|
||||||
button(type="submit") Upload
|
button(type="submit") Upload
|
||||||
p.center #{storageAvailableStr} left.
|
p.center #[b #{storageAvailableStr}] left.
|
||||||
section
|
section
|
||||||
p You can use cURL to upload a file: #[code curl -F 'file=@somefile.ext' https://upload.arav.su]
|
p Using cURL: #[code curl -F 'file=@somefile.ext' https://upload.arav.su]
|
||||||
p Over I2P: #[code curl --proxy 127.0.0.1:4444 -F 'file=@somefile.ext' http://upload.arav.i2p]
|
p Also works under other networks (I2P, Tor, Yggdrasil). For Tor and I2P you'll need to add a #[code --proxy] option for cURL. Same for deletion.
|
||||||
p Same for Tor, just set #[code --proxy] and replace a domain.
|
p A resulted link has the following structure: #[code <site>/<hash>/<file>.<ext>].
|
||||||
p A resulted link looks like this: #[code /base64rawURL(salted SHA-256)/filename.ext].
|
|
||||||
section.center
|
section.center
|
||||||
h2 Delete
|
h2 Delete
|
||||||
form(action="/delete" method="POST")
|
form(action="/delete" method="POST")
|
||||||
input(type="text", name="hash" placeholder="File hash goes here" minlength="43" maxlength="43" size="43" required="")
|
input(type="text", name="hash" placeholder="File hash goes here" minlength="43" maxlength="43" size="43" required="")
|
||||||
button(type="submit") Delete
|
button(type="submit") Delete
|
||||||
section
|
section
|
||||||
p You can delete a file using cURL: #[code curl -XDELETE https://upload.arav.su/<hash>]
|
p Using cURL: #[code curl -XDELETE https://upload.arav.su/<hash>]
|
||||||
p Over I2P: #[code curl --proxy 127.0.0.1:4444 -XDELETE http://upload.arav.i2p/<hash>]
|
|
@ -5,7 +5,7 @@ block header
|
|||||||
|
|
||||||
block body
|
block body
|
||||||
:go:func Uploaded(mainSite, site, downloadLink string, keepForHours int)
|
:go:func Uploaded(mainSite, site, downloadLink string, keepForHours int)
|
||||||
section#file
|
section
|
||||||
h2 Your link
|
h2 Your link
|
||||||
center
|
center
|
||||||
a(href=downloadLink) #{site}#{downloadLink}
|
a(href=downloadLink) #{site}#{downloadLink}
|
||||||
|
Loading…
Reference in New Issue
Block a user