implement file expiration, add basic frontend

This commit is contained in:
noury 2023-07-14 14:11:38 +02:00
parent 4d5f23377a
commit b55740aad7
15 changed files with 220 additions and 18 deletions

3
.gitignore vendored
View file

@ -1,2 +1,5 @@
# Binary
jaf
yaf
yaf.conf
jaf.conf

View file

@ -23,6 +23,7 @@ type Config struct {
ExifAllowedIds []uint16
ExifAllowedPaths []string
ExifAbortOnError bool
FileExpiration bool
}
func ConfigFromFile(filePath string) (*Config, error) {
@ -46,6 +47,7 @@ func ConfigFromFile(filePath string) (*Config, error) {
ExifAllowedIds: []uint16{},
ExifAllowedPaths: []string{},
ExifAbortOnError: true,
FileExpiration: true,
}
scanner := bufio.NewScanner(file)
@ -143,6 +145,13 @@ func ConfigFromFile(filePath string) (*Config, error) {
}
retval.ExifAbortOnError = parsed
case "FileExpiration":
parsed, err := strconv.ParseBool(val)
if err != nil {
return nil, err
}
retval.FileExpiration = parsed
default:
return nil, errors.Errorf("unexpected config key: \"%s\"", key)
}

View file

@ -38,4 +38,5 @@ func TestConfigFromFile(t *testing.T) {
assertEqualSlice(config.ExifAllowedIds, []uint16{0x0112, 274}, t)
assertEqualSlice(config.ExifAllowedPaths, []string{"IFD/Orientation"}, t)
assertEqual(config.ExifAbortOnError, true, t)
assertEqual(config.FileExpiration, true, t)
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

68
dist/index.html vendored Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

2
dist/robots.txt vendored Normal file
View file

@ -0,0 +1,2 @@
User-agent: *
Disallow: /*

4
dist/style.css vendored Normal file
View file

@ -0,0 +1,4 @@
body {
background: #222;
color: #F2F2F2;
}

View file

@ -8,3 +8,4 @@ ScrubExif: true
ExifAllowedIds: 0x0112 274
ExifAllowedPaths: IFD/Orientation
ExifAbortOnError: true
FileExpiration: true

View 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
View file

@ -1,6 +1,6 @@
module github.com/leon-richardt/jaf
module git.sr.ht/~noury/yaf
go 1.19
go 1.20
require (
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/gabriel-vasile/mimetype v1.4.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
)

4
go.sum
View file

@ -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/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=

36
jaf.go
View file

@ -4,23 +4,25 @@ import (
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"time"
"git.sr.ht/~noury/yaf/fileexpiration"
"github.com/julienschmidt/httprouter"
"github.com/leon-richardt/jaf/exifscrubber"
)
const allowedChars = "0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZabcdefghijklmnopqrstuvwxyz"
var config Config
const (
allowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
maxAge = time.Hour * 24 * 7 // 7 days
)
type parameters struct {
configFile string
}
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()
retval := &parameters{}
@ -29,9 +31,6 @@ func parseParams() *parameters {
}
func main() {
rand.Seed(time.Now().UnixNano())
log.SetPrefix("jaf > ")
params := parseParams()
// Read config
@ -39,6 +38,8 @@ func main() {
if err != nil {
log.Fatalf("could not parse config file: %s\n", err.Error())
}
fd := config.FileDir
fmt.Println(fd)
handler := uploadHandler{
config: config,
@ -49,14 +50,19 @@ func main() {
handler.exifScrubber = &scrubber
}
// Start server
uploadServer := &http.Server{
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
Addr: fmt.Sprintf(":%d", config.Port),
if config.FileExpiration {
fmt.Println("FILE EXPIRATION ENABLED")
go func() {
for {
<-time.After(time.Hour * 2)
fileexpiration.DeleteExpired(fd, maxAge)
}
}()
}
router := httprouter.New()
log.Printf("starting jaf on port %d\n", config.Port)
http.Handle("/upload", &handler)
uploadServer.ListenAndServe()
router.HandlerFunc(http.MethodPost, "/upload", handler.PostUpload)
router.HandlerFunc(http.MethodPost, "/uploadweb", handler.PostUploadRedirect)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", config.Port), router))
}

View file

@ -17,9 +17,72 @@ type uploadHandler struct {
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()
// 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")
if err != nil {
http.Error(w, "could not read uploaded file: "+err.Error(), http.StatusBadRequest)