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
|
||||
jaf
|
||||
yaf
|
||||
yaf.conf
|
||||
jaf.conf
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
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
|
||||
ExifAllowedPaths: IFD/Orientation
|
||||
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 (
|
||||
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
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/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
36
jaf.go
|
@ -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 := ¶meters{}
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue