dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

uploads.go (5450B)


      1 package handlers
      2 
      3 import (
      4 	"bytes"
      5 	"dkforest/pkg/captcha"
      6 	"dkforest/pkg/config"
      7 	"dkforest/pkg/database"
      8 	hutils "dkforest/pkg/web/handlers/utils"
      9 	"fmt"
     10 	"github.com/dustin/go-humanize"
     11 	"github.com/labstack/echo"
     12 	"github.com/sirupsen/logrus"
     13 	"io"
     14 	"net/http"
     15 	"unicode/utf8"
     16 )
     17 
     18 func isImageMimeType(mimeType string) bool {
     19 	return mimeType == "image/jpeg" ||
     20 		mimeType == "image/png" ||
     21 		mimeType == "image/gif" ||
     22 		mimeType == "image/bmp" ||
     23 		mimeType == "image/x-icon" ||
     24 		mimeType == "image/webp"
     25 }
     26 
     27 func isAttachmentMimeType(mimeType string) bool {
     28 	return mimeType == "application/x-gzip" ||
     29 		mimeType == "application/zip" ||
     30 		mimeType == "application/x-rar-compressed" ||
     31 		mimeType == "application/pdf" ||
     32 		mimeType == "audio/basic" ||
     33 		mimeType == "audio/aiff" ||
     34 		mimeType == "audio/mpeg" ||
     35 		mimeType == "application/ogg" ||
     36 		mimeType == "audio/midi" ||
     37 		mimeType == "video/avi" ||
     38 		mimeType == "audio/wave" ||
     39 		mimeType == "video/webm" ||
     40 		mimeType == "font/ttf" ||
     41 		mimeType == "font/otf" ||
     42 		mimeType == "font/collection" ||
     43 		mimeType == "font/woff" ||
     44 		mimeType == "font/woff2" ||
     45 		mimeType == "application/wasm" ||
     46 		mimeType == "application/postscript" ||
     47 		mimeType == "application/vnd.ms-fontobject" ||
     48 		mimeType == "application/octet-stream"
     49 }
     50 
     51 func UploadsDownloadHandler(c echo.Context) error {
     52 	authUser := c.Get("authUser").(*database.User)
     53 	db := c.Get("database").(*database.DkfDB)
     54 	filename := c.Param("filename")
     55 	file, err := db.GetUploadByFileName(filename)
     56 	if err != nil {
     57 		return c.Render(http.StatusOK, "standalone.upload404", nil)
     58 	}
     59 	if !file.Exists() {
     60 		logrus.Error(filename + " does not exists")
     61 		return c.Render(http.StatusOK, "standalone.upload404", nil)
     62 	}
     63 
     64 	if file.FileSize < config.MaxFileSizeBeforeDownload {
     65 		fi, decFileBytes, err := file.GetContent()
     66 		if err != nil || fi.IsDir() {
     67 			return c.Render(http.StatusOK, "standalone.upload404", nil)
     68 		}
     69 		buf := bytes.NewReader(decFileBytes)
     70 
     71 		// Validate image type and determine extension
     72 		mimeType, err := getFileContentType(buf)
     73 		_, err = buf.Seek(0, io.SeekStart)
     74 		if err != nil {
     75 			return c.Render(http.StatusOK, "standalone.upload404", nil)
     76 		}
     77 
     78 		// Serve images
     79 		if isImageMimeType(mimeType) {
     80 			http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), buf)
     81 			return nil
     82 		}
     83 
     84 		if mimeType == "application/octet-stream" && utf8.Valid(decFileBytes) {
     85 			mimeType = "text/plain; charset=utf-8"
     86 		}
     87 
     88 		// MimeType that always trigger a file "download"
     89 		if isAttachmentMimeType(mimeType) {
     90 			// Keep track of user downloads
     91 			if _, err := db.CreateDownload(authUser.ID, filename); err != nil {
     92 				logrus.Error(err)
     93 			}
     94 			c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", "attachment", file.OrigFileName))
     95 			http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), buf)
     96 			return nil
     97 		}
     98 
     99 		// Serve any other file as text/plain
    100 		c.Response().Header().Set(echo.HeaderContentType, "text/plain")
    101 		c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("filename=%q", file.OrigFileName))
    102 		http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), buf)
    103 		return nil
    104 	}
    105 
    106 	userNbDownloaded := db.UserNbDownloaded(authUser.ID, filename)
    107 
    108 	// Display captcha to new users, or old users if they already downloaded the file.
    109 	if !authUser.AccountOldEnough() || userNbDownloaded >= 1 {
    110 		// Captcha for bigger files
    111 		var data captchaRequiredData
    112 		data.CaptchaDescription = "Captcha required"
    113 		if !authUser.AccountOldEnough() {
    114 			data.CaptchaDescription = fmt.Sprintf("Account that are less than 3 days old must complete the captcha to download files bigger than %s", humanize.Bytes(config.MaxFileSizeBeforeDownload))
    115 		} else if userNbDownloaded >= 1 {
    116 			data.CaptchaDescription = fmt.Sprintf("For the second download onward of a file bigger than %s, you must complete the captcha", humanize.Bytes(config.MaxFileSizeBeforeDownload))
    117 		}
    118 		data.CaptchaID, data.CaptchaImg = captcha.New()
    119 		const captchaRequiredTmpl = "captcha-required"
    120 		if c.Request().Method == http.MethodGet {
    121 			return c.Render(http.StatusOK, captchaRequiredTmpl, data)
    122 		}
    123 		captchaID := c.Request().PostFormValue("captcha_id")
    124 		captchaInput := c.Request().PostFormValue("captcha")
    125 		if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil {
    126 			data.ErrCaptcha = err.Error()
    127 			return c.Render(http.StatusOK, captchaRequiredTmpl, data)
    128 		}
    129 	}
    130 
    131 	// Keep track of user downloads
    132 	if _, err := db.CreateDownload(authUser.ID, filename); err != nil {
    133 		logrus.Error(err)
    134 	}
    135 
    136 	fi, decFileBytes, err := file.GetContent()
    137 	if err != nil {
    138 		return c.Render(http.StatusOK, "standalone.upload404", nil)
    139 	}
    140 	buf := bytes.NewReader(decFileBytes)
    141 
    142 	c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", "attachment", file.OrigFileName))
    143 	http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), buf)
    144 	return nil
    145 }
    146 
    147 func getFileContentType(out io.ReadSeeker) (string, error) {
    148 
    149 	// Only the first 512 bytes are used to sniff the content type.
    150 	buffer := make([]byte, 512)
    151 
    152 	_, err := out.Read(buffer)
    153 	if err != nil {
    154 		return "", err
    155 	}
    156 
    157 	// Use the net/http package's handy DectectContentType function. Always returns a valid
    158 	// content-type by returning "application/octet-stream" if no others seemed to match.
    159 	contentType := http.DetectContentType(buffer)
    160 
    161 	return contentType, nil
    162 }