mirror of
https://github.com/lyx0/yaf.git
synced 2024-11-13 19:49:53 +01:00
implement file expiration, add basic frontend
This commit is contained in:
parent
4d5f23377a
commit
b55740aad7
15 changed files with 220 additions and 18 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
||||||
# Binary
|
# Binary
|
||||||
jaf
|
jaf
|
||||||
|
yaf
|
||||||
|
yaf.conf
|
||||||
|
jaf.conf
|
||||||
|
|
|
@ -23,6 +23,7 @@ type Config struct {
|
||||||
ExifAllowedIds []uint16
|
ExifAllowedIds []uint16
|
||||||
ExifAllowedPaths []string
|
ExifAllowedPaths []string
|
||||||
ExifAbortOnError bool
|
ExifAbortOnError bool
|
||||||
|
FileExpiration bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigFromFile(filePath string) (*Config, error) {
|
func ConfigFromFile(filePath string) (*Config, error) {
|
||||||
|
@ -46,6 +47,7 @@ func ConfigFromFile(filePath string) (*Config, error) {
|
||||||
ExifAllowedIds: []uint16{},
|
ExifAllowedIds: []uint16{},
|
||||||
ExifAllowedPaths: []string{},
|
ExifAllowedPaths: []string{},
|
||||||
ExifAbortOnError: true,
|
ExifAbortOnError: true,
|
||||||
|
FileExpiration: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
|
@ -143,6 +145,13 @@ func ConfigFromFile(filePath string) (*Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
retval.ExifAbortOnError = parsed
|
retval.ExifAbortOnError = parsed
|
||||||
|
case "FileExpiration":
|
||||||
|
parsed, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.FileExpiration = parsed
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unexpected config key: \"%s\"", key)
|
return nil, errors.Errorf("unexpected config key: \"%s\"", key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,4 +38,5 @@ func TestConfigFromFile(t *testing.T) {
|
||||||
assertEqualSlice(config.ExifAllowedIds, []uint16{0x0112, 274}, t)
|
assertEqualSlice(config.ExifAllowedIds, []uint16{0x0112, 274}, t)
|
||||||
assertEqualSlice(config.ExifAllowedPaths, []string{"IFD/Orientation"}, t)
|
assertEqualSlice(config.ExifAllowedPaths, []string{"IFD/Orientation"}, t)
|
||||||
assertEqual(config.ExifAbortOnError, true, t)
|
assertEqual(config.ExifAbortOnError, true, t)
|
||||||
|
assertEqual(config.FileExpiration, true, t)
|
||||||
}
|
}
|
||||||
|
|
BIN
dist/apple-touch-icon.png
vendored
Normal file
BIN
dist/apple-touch-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 157 B |
BIN
dist/favicon.ico
vendored
Normal file
BIN
dist/favicon.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 B |
68
dist/index.html
vendored
Normal file
68
dist/index.html
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width,initial-scale=1" name="viewport">
|
||||||
|
<title>yaf - yet another fileshare</title>
|
||||||
|
<meta property="og:title" content="yaf - yet another fileshare">
|
||||||
|
<meta property="og:description" content="a privacy conscious place to share your screenshots">
|
||||||
|
<meta property="og:image" content="https://yaf.ee/og.png">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<pre>
|
||||||
|
yaf.ee - yet another (temporary) fileshare
|
||||||
|
- all uploads will be deleted after 7 days
|
||||||
|
- maximum file size is 50Mb
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<form action="https://i.yaf.ee/uploadweb" method="post" enctype="multipart/form-data" >
|
||||||
|
<div>
|
||||||
|
<input type="file" id="file" name="file" />
|
||||||
|
<button>Upload</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
from your terminal as POST:
|
||||||
|
$ curl -L -F "file=@/home/noury/xd.txt" i.yaf.ee/upload
|
||||||
|
|
||||||
|
from chatterino:
|
||||||
|
Request URL: https://i.yaf.ee/upload
|
||||||
|
Form field: file
|
||||||
|
leave the rest empty
|
||||||
|
|
||||||
|
from dankchat:
|
||||||
|
Upload url: https://i.yaf.ee/upload
|
||||||
|
Form field: file
|
||||||
|
leave the rest empty
|
||||||
|
|
||||||
|
from sharenix:
|
||||||
|
"Services": [
|
||||||
|
{
|
||||||
|
"Name": "i.yaf.ee",
|
||||||
|
"RequestType": "POST",
|
||||||
|
"RequestURL": "https://i.yaf.ee/upload",
|
||||||
|
"FileFormName": "file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------------
|
||||||
|
i tried to build this site as a minimalistic and privacy conscious filesharing host mostly
|
||||||
|
meant for sharing screenshots with your friends without having to stumble onto your old
|
||||||
|
cringy screenshots 7 years later when someone looks through old logs.
|
||||||
|
|
||||||
|
terms of service:
|
||||||
|
1. you agree that this is beautiful web design.
|
||||||
|
2. do not upload anything that is either:
|
||||||
|
2.1 illegal in luxembourg
|
||||||
|
2.2 will annoy my hosting provider buyvm.net
|
||||||
|
|
||||||
|
privacy policy:
|
||||||
|
- i dont store logs
|
||||||
|
|
||||||
|
this site and its storage are hosted by <a href="https://buyvm.net"/>https://buyvm.net</a> in a datacenter located in luxembourg.
|
||||||
|
--------------------------------------------------------------------------------------
|
||||||
|
if you need to contact me or want your file removed send an email to noury[at]yaf.ee
|
||||||
|
source: <a href="https://git.sr.ht/~noury/yaf">https://git.sr.ht/~noury/yaf</a>
|
||||||
|
</pre>
|
||||||
|
</html>
|
BIN
dist/og.png
vendored
Normal file
BIN
dist/og.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
2
dist/robots.txt
vendored
Normal file
2
dist/robots.txt
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /*
|
4
dist/style.css
vendored
Normal file
4
dist/style.css
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
body {
|
||||||
|
background: #222;
|
||||||
|
color: #F2F2F2;
|
||||||
|
}
|
|
@ -8,3 +8,4 @@ ScrubExif: true
|
||||||
ExifAllowedIds: 0x0112 274
|
ExifAllowedIds: 0x0112 274
|
||||||
ExifAllowedPaths: IFD/Orientation
|
ExifAllowedPaths: IFD/Orientation
|
||||||
ExifAbortOnError: true
|
ExifAbortOnError: true
|
||||||
|
FileExpiration: true
|
||||||
|
|
39
fileexpiration/file_expiration.go
Normal file
39
fileexpiration/file_expiration.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package fileexpiration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
deleteIgnoreRegexp = regexp.MustCompile("index\\.html|favicon\\.ico")
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeleteExpired(fd string, maxAge time.Duration) {
|
||||||
|
files, err := ioutil.ReadDir(fd)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
fname := file.Name()
|
||||||
|
|
||||||
|
if file.IsDir() || deleteIgnoreRegexp.MatchString(fname) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(file.ModTime()) > maxAge {
|
||||||
|
err := os.Remove(fd + fname)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Removed %s \n", fname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
go.mod
6
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module github.com/leon-richardt/jaf
|
module git.sr.ht/~noury/yaf
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e
|
github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e
|
||||||
|
@ -9,6 +9,8 @@ require (
|
||||||
github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d
|
github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d
|
||||||
github.com/gabriel-vasile/mimetype v1.4.1
|
github.com/gabriel-vasile/mimetype v1.4.1
|
||||||
github.com/go-errors/errors v1.1.1
|
github.com/go-errors/errors v1.1.1
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
github.com/leon-richardt/jaf v0.3.1
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -30,6 +30,10 @@ github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgR
|
||||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
|
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
|
||||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/leon-richardt/jaf v0.3.1 h1:af6p+xr7KX9OxwuiUHQSG8yVJQppugpW+iS+ptIQ0bM=
|
||||||
|
github.com/leon-richardt/jaf v0.3.1/go.mod h1:rvBGLF1s8u3XNUTEqGeOSYVoEnIIwI/pKeTEdMdKtJk=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||||
|
|
36
jaf.go
36
jaf.go
|
@ -4,23 +4,25 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.sr.ht/~noury/yaf/fileexpiration"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/leon-richardt/jaf/exifscrubber"
|
"github.com/leon-richardt/jaf/exifscrubber"
|
||||||
)
|
)
|
||||||
|
|
||||||
const allowedChars = "0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZabcdefghijklmnopqrstuvwxyz"
|
const (
|
||||||
|
allowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
var config Config
|
maxAge = time.Hour * 24 * 7 // 7 days
|
||||||
|
)
|
||||||
|
|
||||||
type parameters struct {
|
type parameters struct {
|
||||||
configFile string
|
configFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseParams() *parameters {
|
func parseParams() *parameters {
|
||||||
configFile := flag.String("configFile", "jaf.conf", "path to config file")
|
configFile := flag.String("configFile", "yaf.conf", "path to config file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
retval := ¶meters{}
|
retval := ¶meters{}
|
||||||
|
@ -29,9 +31,6 @@ func parseParams() *parameters {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
log.SetPrefix("jaf > ")
|
|
||||||
|
|
||||||
params := parseParams()
|
params := parseParams()
|
||||||
|
|
||||||
// Read config
|
// Read config
|
||||||
|
@ -39,6 +38,8 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("could not parse config file: %s\n", err.Error())
|
log.Fatalf("could not parse config file: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
|
fd := config.FileDir
|
||||||
|
fmt.Println(fd)
|
||||||
|
|
||||||
handler := uploadHandler{
|
handler := uploadHandler{
|
||||||
config: config,
|
config: config,
|
||||||
|
@ -49,14 +50,19 @@ func main() {
|
||||||
handler.exifScrubber = &scrubber
|
handler.exifScrubber = &scrubber
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start server
|
if config.FileExpiration {
|
||||||
uploadServer := &http.Server{
|
fmt.Println("FILE EXPIRATION ENABLED")
|
||||||
ReadTimeout: 30 * time.Second,
|
go func() {
|
||||||
WriteTimeout: 30 * time.Second,
|
for {
|
||||||
Addr: fmt.Sprintf(":%d", config.Port),
|
<-time.After(time.Hour * 2)
|
||||||
|
fileexpiration.DeleteExpired(fd, maxAge)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router := httprouter.New()
|
||||||
log.Printf("starting jaf on port %d\n", config.Port)
|
log.Printf("starting jaf on port %d\n", config.Port)
|
||||||
http.Handle("/upload", &handler)
|
router.HandlerFunc(http.MethodPost, "/upload", handler.PostUpload)
|
||||||
uploadServer.ListenAndServe()
|
router.HandlerFunc(http.MethodPost, "/uploadweb", handler.PostUploadRedirect)
|
||||||
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", config.Port), router))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,72 @@ type uploadHandler struct {
|
||||||
exifScrubber *exifscrubber.ExifScrubber
|
exifScrubber *exifscrubber.ExifScrubber
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *uploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (handler *uploadHandler) PostUploadRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
// Limit size to 50Mb
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
|
||||||
|
uploadFile, header, err := r.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "could not read uploaded file: "+err.Error(), http.StatusBadRequest)
|
||||||
|
log.Println(" could not read uploaded file: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileData, err := io.ReadAll(uploadFile)
|
||||||
|
uploadFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "could not read attached file: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
log.Println(" could not read attached file: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrub EXIF, if requested and detectable by us
|
||||||
|
if handler.config.ScrubExif {
|
||||||
|
scrubbedData, err := handler.exifScrubber.ScrubExif(fileData[:])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// If scrubbing was successful, update what to write to file
|
||||||
|
fileData = scrubbedData
|
||||||
|
} else {
|
||||||
|
// Unknown file types (not PNG or JPEG) are allowed to contain EXIF, as we don't know
|
||||||
|
// how to handle them. Handling of other errors depends on configuration.
|
||||||
|
if err != exifscrubber.ErrUnknownFileType {
|
||||||
|
if handler.config.ExifAbortOnError {
|
||||||
|
log.Printf("could not scrub EXIF from file, aborting upload: %s", err.Error())
|
||||||
|
http.Error(
|
||||||
|
w,
|
||||||
|
"could not scrub EXIF from file: "+err.Error(),
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// An error occured but we are configured to proceed with the upload anyway
|
||||||
|
log.Printf(
|
||||||
|
"could not scrub EXIF from file but proceeding with upload as configured: %s",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := generateLink(handler, fileData[:], header.Filename)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "could not save file: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
log.Println(" could not save file: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicitly means code 200
|
||||||
|
http.Redirect(w, r, link, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *uploadHandler) PostUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
// Limit size to 50Mb
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
|
||||||
uploadFile, header, err := r.FormFile("file")
|
uploadFile, header, err := r.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "could not read uploaded file: "+err.Error(), http.StatusBadRequest)
|
http.Error(w, "could not read uploaded file: "+err.Error(), http.StatusBadRequest)
|
||||||
|
|
Loading…
Reference in a new issue