Add mime types and return page

Adds mime type handling and return page when doing a file upload
This commit is contained in:
Felix Niederwanger 2020-12-15 17:09:41 +01:00 committed by felix@feldspaten.org
parent 01be8bd742
commit 662187c3fc
2 changed files with 161 additions and 21 deletions

View file

@ -5,6 +5,7 @@
package main
import (
"bufio"
"errors"
"fmt"
"io"
@ -20,15 +21,17 @@ import (
)
type Config struct {
BaseUrl string `toml:"BaseURL"` // Instance base URL
PastaDir string `toml:"PastaDir"` // dir where pasta are stored
BindAddr string `toml:"BindAddress"`
MaxBinSize int64 `toml:"MaxBinSize"` // Max bin size in bytes
BinCharacters int `toml:"BinCharacters"`
BaseUrl string `toml:"BaseURL"` // Instance base URL
PastaDir string `toml:"PastaDir"` // dir where pasta are stored
BindAddr string `toml:"BindAddress"`
MaxPastaSize int64 `toml:"MaxPastaSize"` // Max bin size in bytes
PastaCharacters int `toml:"PastaCharacters"`
MimeTypesFile string `toml:"MimeTypes` // Load mime types from this file
}
var cf Config
var bowl PastaBowl
var mimeExtensions map[string]string
func ExtractPastaId(path string) string {
i := strings.LastIndex(path, "/")
@ -39,6 +42,37 @@ func ExtractPastaId(path string) string {
}
}
/* Load MIME types file. MIME types file is a simple text file that describes mime types based on file extenstions.
* The format of the file is
* EXTENSION = MIMETYPE
*/
func loadMimeTypes(filename string) (map[string]string, error) {
ret := make(map[string]string, 0)
file, err := os.OpenFile(filename, os.O_RDONLY, 0400)
if err != nil {
return ret, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || line[0] == '#' {
continue
}
i := strings.Index(line, "=")
if i < 0 {
continue
}
name, value := strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:])
if name != "" && value != "" {
ret[name] = value
}
}
return ret, scanner.Err()
}
func SendPasta(pasta Pasta, w http.ResponseWriter) error {
file, err := bowl.GetPastaReader(pasta.Id)
if err != nil {
@ -68,7 +102,7 @@ func receiveBody(reader io.Reader, pasta *Pasta) error {
}
defer file.Close()
pasta.Size = 0
for pasta.Size < cf.MaxBinSize {
for pasta.Size < cf.MaxPastaSize {
n, err := reader.Read(buf)
if (err == nil || err == io.EOF) && n > 0 {
if _, err = file.Write(buf[:n]); err != nil {
@ -88,8 +122,21 @@ func receiveBody(reader io.Reader, pasta *Pasta) error {
return nil
}
/* try to determine the mime type by file extension. Returns empty string on failure */
func mimeByFilename(filename string) string {
i := strings.LastIndex(filename, ".")
if i < 0 {
return ""
}
extension := filename[i+1:]
if mime, ok := mimeExtensions[extension]; ok {
return mime
}
return ""
}
func receiveMultibody(r *http.Request, pasta *Pasta) (io.ReadCloser, error) {
err := r.ParseMultipartForm(cf.MaxBinSize)
err := r.ParseMultipartForm(cf.MaxPastaSize)
if err != nil {
return nil, err
}
@ -98,7 +145,10 @@ func receiveMultibody(r *http.Request, pasta *Pasta) (io.ReadCloser, error) {
return nil, err
}
pasta.AttachmentFilename = header.Filename
// TODO: Mime type
// Determine MIME type based on file extension, if present
if pasta.AttachmentFilename != "" {
pasta.Mime = mimeByFilename(pasta.AttachmentFilename)
}
return file, err
}
@ -108,7 +158,7 @@ func ReceivePasta(r *http.Request) (Pasta, error) {
// TODO: Use suggested ID from http header if present
pasta.Id = bowl.GenerateRandomBinId(cf.BinCharacters)
pasta.Id = bowl.GenerateRandomBinId(cf.PastaCharacters)
// Note InsertPasta sets the filename
if err = bowl.InsertPasta(&pasta); err != nil {
return pasta, err
@ -127,7 +177,7 @@ func ReceivePasta(r *http.Request) (Pasta, error) {
if err := receiveBody(reader, &pasta); err != nil {
return pasta, err
}
if pasta.Size >= cf.MaxBinSize {
if pasta.Size >= cf.MaxPastaSize {
log.Println("Max size exceeded while receiving bin")
return pasta, errors.New("Bin size exceeded")
}
@ -159,9 +209,28 @@ func handlerPost(w http.ResponseWriter, r *http.Request) {
log.Printf("Received bin %s (%d bytes) from %s", pasta.Id, pasta.Size, r.RemoteAddr)
w.WriteHeader(http.StatusOK)
url := fmt.Sprintf("%s/%s", cf.BaseUrl, pasta.Id)
// Dont use json package, the reply is simple enough to build it on-the-fly
reply := fmt.Sprintf("{\"url\":\"%s\",\"token\":\"%s\"}", url, pasta.Token)
w.Write([]byte(reply))
// Return format
retFormats := r.URL.Query()["ret"]
retFormat := ""
if len(retFormats) > 0 {
retFormat = retFormats[0]
}
if retFormat == "html" {
// Website as return format
fmt.Fprintf(w, "<!doctype html><html><head><title>pasta</title></head>\n")
fmt.Fprintf(w, "<body>\n")
fmt.Fprintf(w, "<h1>pasta</h1>\n")
fmt.Fprintf(w, "<p>Stupid simple pastebin service</p>\n")
fmt.Fprintf(w, "<p>Pasta link: <a href=\"%s\">%s</a></p>\n", url, url)
fmt.Fprintf(w, "<h2>Token</h2>\n")
fmt.Fprintf(w, "<pre>%s</pre>\n", pasta.Token)
fmt.Fprintf(w, "<p>Use the token to modify your pasta</p>\n")
fmt.Fprintf(w, "</body></html>")
} else {
// Dont use json package, the reply is simple enough to build it on-the-fly
reply := fmt.Sprintf("{\"url\":\"%s\",\"token\":\"%s\"}", url, pasta.Token)
w.Write([]byte(reply))
}
}
}
}
@ -205,8 +274,8 @@ func handlerIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<body>\n")
fmt.Fprintf(w, "<h1>pasta</h1>\n")
fmt.Fprintf(w, "<p>Stupid simple pastebin service</p>\n")
fmt.Fprintf(w, "<form enctype=\"multipart/form-data\" method=\"post\">\n")
fmt.Fprintf(w, "<input type=\"file\" name=\"file\" name=\"filename\">\n")
fmt.Fprintf(w, "<form enctype=\"multipart/form-data\" method=\"post\" action=\"/?ret=html\">\n")
fmt.Fprintf(w, "<input type=\"file\" name=\"file\">\n")
fmt.Fprintf(w, "<input type=\"submit\" value=\"Upload\">\n")
fmt.Fprintf(w, "</form>\n")
fmt.Fprintf(w, "</body></html>")
@ -218,8 +287,9 @@ func main() {
cf.BaseUrl = "http://localhost:8199"
cf.PastaDir = "pastas/"
cf.BindAddr = "127.0.0.1:8199"
cf.MaxBinSize = 1024 * 1024 * 25 // Default max size: 25 MB
cf.BinCharacters = 8 // Note: Never use less than 8 characters!
cf.MaxPastaSize = 1024 * 1024 * 25 // Default max size: 25 MB
cf.PastaCharacters = 8 // Note: Never use less than 8 characters!
cf.MimeTypesFile = "mime.types"
rand.Seed(time.Now().Unix())
fmt.Println("Starting pasta server ... ")
if FileExists(configFile) {
@ -232,8 +302,8 @@ func main() {
}
// Sanity check
if cf.BinCharacters < 8 {
log.Println("Warning: Using less than 8 bin characters is recommended and might lead to unintended side-effects")
if cf.PastaCharacters < 8 {
log.Println("Warning: Using less than 8 pasta characters is not recommended and might lead to unintended side-effects")
}
if cf.PastaDir == "" {
cf.PastaDir = "."
@ -241,10 +311,22 @@ func main() {
bowl.Directory = cf.PastaDir
os.Mkdir(bowl.Directory, os.ModePerm)
// Load MIME types file
if cf.MimeTypesFile == "" {
mimeExtensions = make(map[string]string, 0)
} else {
var err error
mimeExtensions, err = loadMimeTypes(cf.MimeTypesFile)
if err != nil {
log.Printf("Warning: Cannot load mime types file '%s': %s", cf.MimeTypesFile, err)
} else {
log.Printf("Loaded %d mime types", len(mimeExtensions))
}
}
// Setup webserver
log.Printf("Webserver initialization: http://%s", cf.BindAddr)
http.HandleFunc("/", handler)
http.HandleFunc("/health", handlerHealth)
log.Printf("Startup completed. Serving http://%s", cf.BindAddr)
log.Printf("Serving http://%s", cf.BindAddr)
log.Fatal(http.ListenAndServe(cf.BindAddr, nil))
}

58
mime.types Normal file
View file

@ -0,0 +1,58 @@
# Known mime types based on file extension
bmp = image/bmp
bz = application/x-bzip
bz2 = application/x-bzip2
csh = application/x-csh
css = text/csvv
csv = text/csv
doc = application/msword
docx = application/vnd.openxmlformats-officedocument.wordprocessingml.document
epub = application/epub+zip
gz = application/gzip
gif = image/gif
htm = text/html
html = text/html
ics = text/calendar
jar = application/java-archive
jpg = image/jpeg
jpeg = image/jpeg
js = text/javascript
json = application/json
mp3 = audio/mpeg
mpg = audio/mpeg
mpeg = audio/mpeg
ods = application/vnd.oasis.opendocument.presentation
otd = application/vnd.oasis.opendocument.spreadsheet
otd = application/vnd.oasis.opendocument.text
oga = audio/ogg
ogv = audio/ogg
opus = audio/opus
otf = font/otf
png = image/png
pdf = application/pdf
php = application/x-httpd-php
ppt = application/vnd.ms-powerpoint
pptx = application/vnd.openxmlformats-officedocument.presentationml.presentation
rat = application/vnd.rar
rtf = application/rtf
sh = application/x-sh
svg = image/svg+xml
tar = application/x-tar
tif = image/tiff
tiff = image/tiff
ts = video/mp2t
ttf = font/ttf
txt = text/plain
wav = audio/wav
weba = audio/webm
webm = video/webm
webp = image/webp
woff = font/woff
woff2 = font/woff2
xhtml = application/xhtml+xml
xls = application/vnd.ms-excel
xlsx = application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xml = text/xml
xz = application/x-xz
zip = application/zip
7z = application/x-7z-compressed