First!!
This commit is contained in:
commit
4c6b88d530
|
@ -0,0 +1,35 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/db"
|
||||
)
|
||||
|
||||
func GetAuthCookie(r *http.Request) string {
|
||||
cookies := r.Cookies()
|
||||
for i, _ := range(cookies) {
|
||||
if cookies[i].Name == "auth" {
|
||||
return cookies[i].Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
auth = GetAuthCookie(r)
|
||||
}
|
||||
|
||||
if auth != "" && db.Db.IsAuthKeyExists(auth) {
|
||||
log.Printf("Accepted auth header: '%s'\n", auth)
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
log.Printf("Rejected auth header: '%s'\n", auth)
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
_ "fmt"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port uint16
|
||||
Address string
|
||||
DBpath string
|
||||
StoreDir string
|
||||
UrlPrefix string
|
||||
IdLen int
|
||||
AuthKeyLen int
|
||||
}
|
||||
|
||||
var Conf = Config{}
|
||||
|
||||
func LoadConfig(path string) (Config, error) {
|
||||
conf := Config{}
|
||||
|
||||
cont, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return conf, err
|
||||
}
|
||||
err = json.Unmarshal(cont, &conf)
|
||||
Conf = conf
|
||||
return conf, err
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/db"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/config"
|
||||
sauth "git.fuwafuwa.moe/x3/ngfshare/auth"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func deleteFile(sha1sum string) error {
|
||||
d1 := sha1sum[0:2]
|
||||
d2 := sha1sum[2:4]
|
||||
path := fmt.Sprintf("%s/%s/%s/%s", config.Conf.StoreDir, d1, d2, sha1sum)
|
||||
|
||||
// This leaves empty directories, but whatever
|
||||
err := os.Remove(path)
|
||||
return err
|
||||
}
|
||||
|
||||
func Delete(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
auth = sauth.GetAuthCookie(r)
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
file, err := db.Db.GetFileById(id)
|
||||
if err != nil {
|
||||
log.Println("Delete: File by id", id, "not found in databse", err)
|
||||
http.Error(w, `{"status":"Not found"}`, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if file.UploadKey != auth {
|
||||
log.Println("Trying to delete a file uploaded by a different key", id)
|
||||
http.Error(w, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
ex, err := db.Db.DeleteFile(id)
|
||||
if err != nil {
|
||||
log.Println("Failed to delete file with id", id, err)
|
||||
http.Error(w, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !ex {
|
||||
log.Println("Tried to delete file that doens't exists", id)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
err = deleteFile(file.Sha1Sum)
|
||||
if err != nil {
|
||||
log.Println("Cannot delete file with sum", file.Sha1Sum, err)
|
||||
}
|
||||
|
||||
log.Println("Deleted file", id)
|
||||
|
||||
/*
|
||||
if auth.GetAuthCookie() != "" {
|
||||
// Called from the webpage, return redirect
|
||||
}
|
||||
*/
|
||||
|
||||
fmt.Fprintln(w, `{"status":"OK"}`)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/db"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func Download(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
file, err := db.Db.GetFileById(id)
|
||||
if err != nil {
|
||||
log.Println("File by id", id, "not found in databse", err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
d1 := file.Sha1Sum[0:2]
|
||||
d2 := file.Sha1Sum[2:4]
|
||||
|
||||
w.Header().Add("Content-Disposition", fmt.Sprintf("filename=\"%s\"", file.Filename))
|
||||
w.Header().Add("Content-Type", file.ContentType)
|
||||
w.Header().Add("X-Accel-Expires", "1800")
|
||||
w.Header().Add("X-Accel-Redirect", fmt.Sprintf("/store/%s/%s/%s", d1, d2, file.Sha1Sum))
|
||||
|
||||
log.Printf("Served file with id: '%s' sha1sum: '%s'", file.Id, file.Sha1Sum)
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"crypto/sha1"
|
||||
"os"
|
||||
"mime/multipart"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/config"
|
||||
sauth "git.fuwafuwa.moe/x3/ngfshare/auth"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/db"
|
||||
)
|
||||
|
||||
type responseStruct struct {
|
||||
Id string `json:"id"`
|
||||
Filename string `json:"filename"`
|
||||
Url string `json:"url"`
|
||||
UrlShort string `json:"url_short"`
|
||||
DeleteUrl string `json:"delete_url"`
|
||||
}
|
||||
func responseWithFile(id, filename string, w http.ResponseWriter) {
|
||||
urlShort := fmt.Sprintf("%s/-%s", config.Conf.UrlPrefix, id)
|
||||
rsp := responseStruct{
|
||||
Id: id,
|
||||
Filename: filename,
|
||||
Url: fmt.Sprintf("%s/%s", urlShort, filename),
|
||||
UrlShort: urlShort,
|
||||
DeleteUrl: fmt.Sprintf("%s/api/delete/%s", config.Conf.UrlPrefix, id),
|
||||
}
|
||||
jsb, err := json.Marshal(rsp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintln(w, `{"error":"Failed response json marshal"}`)
|
||||
return
|
||||
}
|
||||
w.Write(jsb)
|
||||
}
|
||||
|
||||
func copyFileToStorage(srcFile multipart.File, sha1sum string) error {
|
||||
d1 := sha1sum[0:2]
|
||||
d2 := sha1sum[2:4]
|
||||
saveDir := fmt.Sprintf("%s/%s/%s", config.Conf.StoreDir, d1, d2)
|
||||
savePath := fmt.Sprintf("%s/%s", saveDir, sha1sum)
|
||||
|
||||
err := os.MkdirAll(saveDir, 0750)
|
||||
if err != nil {
|
||||
log.Println("Cannot create directory for savedir:", saveDir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
dstFile, err := os.Create(savePath)
|
||||
if err != nil {
|
||||
log.Println("Cannot create file for savepath:", savePath, err)
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
_, err = io.Copy(dstFile, srcFile)
|
||||
if err != nil {
|
||||
log.Println("Cannot copy file", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("File copied")
|
||||
return nil
|
||||
}
|
||||
|
||||
func Upload(w http.ResponseWriter, r *http.Request) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
cookieAuth := false
|
||||
if auth == "" {
|
||||
auth = sauth.GetAuthCookie(r)
|
||||
cookieAuth = true
|
||||
}
|
||||
r.ParseMultipartForm(40*1024*1024)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
file, fheader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
log.Println("No file POST field in upload, or some other error", err)
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
sha := sha1.New()
|
||||
hlen, err := io.Copy(sha, file)
|
||||
if err != nil || hlen != fheader.Size {
|
||||
log.Println("Hash copy failed or not all file got hashed", err)
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
}
|
||||
sum := fmt.Sprintf("%x", sha.Sum(nil))
|
||||
|
||||
dbFile, exists := db.Db.GetFileBySha1(sum)
|
||||
if exists {
|
||||
log.Println("Dupe upload detected on id", dbFile.Id)
|
||||
// Don't check if it was uploaded by the same api key or not because i don't care.
|
||||
if !cookieAuth {
|
||||
responseWithFile(dbFile.Id, dbFile.Filename, w)
|
||||
} else {
|
||||
// If upload from web, redirect to the final url
|
||||
http.Redirect(w, r, fmt.Sprintf("/-%s/%s", dbFile.Id, dbFile.Filename), http.StatusFound)
|
||||
}
|
||||
return
|
||||
}
|
||||
file.Seek(0, 0)
|
||||
b := make([]byte, 512)
|
||||
file.Read(b)
|
||||
fType := http.DetectContentType(b)
|
||||
if fType == "application/octet-stream" {
|
||||
fType = fheader.Header.Get("Content-Type")
|
||||
}
|
||||
|
||||
resId, tx, err := db.Db.InsertFile(fheader.Filename, fheader.Size, fType, sum, auth)
|
||||
if err != nil {
|
||||
log.Println("Failed to insert into database", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintln(w, `{"error":"Failed to insert into databse"}`)
|
||||
return
|
||||
}
|
||||
|
||||
file.Seek(0, 0)
|
||||
err = copyFileToStorage(file, sum)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
fmt.Fprintln(w, `{"error":"Failed to copy file"}`)
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
log.Printf("Added file with id: '%s' name: '%s' sha1sum: '%s'", resId, fheader.Filename, sum)
|
||||
if !cookieAuth {
|
||||
responseWithFile(resId, fheader.Filename, w)
|
||||
} else {
|
||||
// If upload from web, redirect to the final url
|
||||
http.Redirect(w, r, fmt.Sprintf("/-%s/%s", resId, fheader.Filename), http.StatusFound)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"log"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/view"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/db"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/auth"
|
||||
)
|
||||
|
||||
func webGetList(w http.ResponseWriter, r *http.Request, auth string) {
|
||||
log.Println("In webget file list")
|
||||
|
||||
files, err := db.Db.GetFilesByAuthKey(auth)
|
||||
if err != nil {
|
||||
log.Println("Failed to get files by auth key", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = view.Execute("files", w, files)
|
||||
if err != nil {
|
||||
log.Println("Error in webGetList:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func webGetLogin(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("In webget login")
|
||||
err := view.Execute("login", w, "")
|
||||
if err != nil {
|
||||
log.Println("Error in webGetLogin:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func WebGet(w http.ResponseWriter, r *http.Request) {
|
||||
auth := auth.GetAuthCookie(r)
|
||||
authOk := auth != "" && db.Db.IsAuthKeyExists(auth)
|
||||
|
||||
if authOk {
|
||||
webGetList(w, r, auth)
|
||||
} else {
|
||||
webGetLogin(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func WebLogin(w http.ResponseWriter, r *http.Request) {
|
||||
auth := r.FormValue("auth")
|
||||
ok := db.Db.IsAuthKeyExists(auth)
|
||||
if !ok {
|
||||
http.Error(w, "Authentication failure", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Successfull web auth for key", auth)
|
||||
cookie := http.Cookie{
|
||||
Name: "auth",
|
||||
Value: auth,
|
||||
}
|
||||
http.SetCookie(w, &cookie)
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
func WebLogout(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("In logout")
|
||||
cookie := http.Cookie{
|
||||
Name: "auth",
|
||||
Value: "",
|
||||
MaxAge: -1,
|
||||
}
|
||||
http.SetCookie(w, &cookie)
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"log"
|
||||
"database/sql"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/id"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/model"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
ctx *sql.DB
|
||||
}
|
||||
|
||||
var Db *DB
|
||||
|
||||
func createTables(ctx *sql.DB) error {
|
||||
_, err := ctx.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS keys (
|
||||
key TEXT PRIMARY KEY UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS files (
|
||||
id TEXT PRIMARY KEY UNIQUE,
|
||||
filename TEXT,
|
||||
size INTEGER,
|
||||
content_type TEXT,
|
||||
upload_time INTEGER,
|
||||
sha1sum TEXT UNIQUE,
|
||||
uploadKey TEXT,
|
||||
FOREIGN KEY (uploadKey)
|
||||
REFERENCES keys(key)
|
||||
);
|
||||
`)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func Open(path string) (*DB, error) {
|
||||
log.Printf("Opening DB at '%s'\n", path)
|
||||
|
||||
ctx, err := sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
log.Println("Failed to open DB", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = createTables(ctx)
|
||||
if err != nil {
|
||||
log.Println("Failed to create tables", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Db = &DB{
|
||||
ctx: ctx,
|
||||
}
|
||||
return Db, nil
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
err := db.ctx.Close()
|
||||
Db = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) IsAuthKeyExists(key string) bool {
|
||||
row := db.ctx.QueryRow(`
|
||||
SELECT EXISTS(SELECT 1 FROM keys WHERE key = ?);
|
||||
`, key)
|
||||
var ex int
|
||||
row.Scan(&ex)
|
||||
return ex == 1
|
||||
}
|
||||
|
||||
func (db *DB) CreateNewAuthKey() (string, error) {
|
||||
var err error
|
||||
key := id.GenAuthKey()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = db.ctx.Exec(`
|
||||
INSERT INTO keys (
|
||||
key
|
||||
)
|
||||
VALUES (?)
|
||||
`, key)
|
||||
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
/*
|
||||
errorCode := err.(sqll.Error).Code
|
||||
log.Println(errorCode)
|
||||
log.Println(err)
|
||||
if errorCode != sqll.ErrConstraint {
|
||||
log.Println("HERERERE")
|
||||
break
|
||||
}
|
||||
*/
|
||||
|
||||
// Try again
|
||||
key = id.GenAuthKey()
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
func (db *DB) GetFileBySha1(sum string) (model.File, bool) {
|
||||
row := db.ctx.QueryRow(`
|
||||
SELECT
|
||||
id, filename, size, content_type, upload_time, uploadKey
|
||||
FROM
|
||||
files
|
||||
WHERE
|
||||
sha1sum = ?
|
||||
;`, sum)
|
||||
|
||||
f := model.File{Sha1Sum: sum}
|
||||
err := row.Scan(&f.Id, &f.Filename, &f.Size, &f.ContentType, &f.UploadTime, &f.UploadKey)
|
||||
//log.Println("db: ", err)
|
||||
return f, err == nil
|
||||
}
|
||||
|
||||
func (db *DB) GetFileById(id string) (model.File, error) {
|
||||
row := db.ctx.QueryRow(`
|
||||
SELECT
|
||||
filename, size, content_type, upload_time, sha1sum, uploadKey
|
||||
FROM
|
||||
files
|
||||
WHERE
|
||||
id = ?
|
||||
;`, id)
|
||||
|
||||
f := model.File{Id: id}
|
||||
err := row.Scan(&f.Filename, &f.Size, &f.ContentType, &f.UploadTime, &f.Sha1Sum, &f.UploadKey)
|
||||
//log.Println("db: ", err)
|
||||
return f, err
|
||||
}
|
||||
|
||||
func (db *DB) GetFilesByAuthKey(key string) ([]model.File, error) {
|
||||
lst := make([]model.File, 0, 16)
|
||||
rows, err := db.ctx.Query(`
|
||||
SELECT
|
||||
id, filename, size, content_type, upload_time, sha1sum
|
||||
FROM
|
||||
files
|
||||
WHERE
|
||||
uploadKey = ?
|
||||
ORDER BY
|
||||
upload_time DESC
|
||||
;`, key)
|
||||
if err != nil {
|
||||
return lst, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
f := model.File{
|
||||
UploadKey: key,
|
||||
}
|
||||
err = rows.Scan(&f.Id, &f.Filename, &f.Size, &f.ContentType, &f.UploadTime, &f.Sha1Sum)
|
||||
if err != nil {
|
||||
return lst, err
|
||||
}
|
||||
lst = append(lst, f)
|
||||
}
|
||||
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
func (db *DB) InsertFile(filename string, size int64, content_type, sha1sum, uploadKey string) (string, *sql.Tx, error) {
|
||||
var err error
|
||||
tx, err := db.ctx.Begin()
|
||||
if err != nil {
|
||||
log.Println("Cannot start Tx", err)
|
||||
return "", nil, err
|
||||
}
|
||||
fId := id.GenFileId()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO files (
|
||||
id,
|
||||
size,
|
||||
filename,
|
||||
content_type,
|
||||
upload_time,
|
||||
sha1sum,
|
||||
uploadKey
|
||||
)
|
||||
VALUES(?,?,?,?,strftime('%s', 'now'),?,?)
|
||||
`, fId, size, filename, content_type, sha1sum, uploadKey)
|
||||
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fId = id.GenFileId()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
|
||||
return fId, tx, err
|
||||
}
|
||||
|
||||
func (db *DB) DeleteFile(id string) (bool, error) {
|
||||
res, err := db.ctx.Exec(`
|
||||
DELETE FROM files
|
||||
WHERE id = ?;
|
||||
`, id)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
return n != 0, nil
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module git.fuwafuwa.moe/x3/ngfshare
|
||||
|
||||
go 1.21.6
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
|
@ -0,0 +1,28 @@
|
|||
package id
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/config"
|
||||
)
|
||||
|
||||
var idChars = []rune("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789")
|
||||
|
||||
func genRandStr(length int) string {
|
||||
var b strings.Builder
|
||||
b.Grow(length)
|
||||
for i := 0; i < length; i++ {
|
||||
rndIdx := rand.Intn(len(idChars))
|
||||
b.WriteRune(idChars[rndIdx])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func GenFileId() string {
|
||||
return genRandStr(config.Conf.IdLen)
|
||||
}
|
||||
|
||||
func GenAuthKey() string {
|
||||
return genRandStr(config.Conf.AuthKeyLen)
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/config"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/net"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/db"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/view"
|
||||
);
|
||||
|
||||
func main() {
|
||||
|
||||
confFilePath := flag.String("config", "", "The path for the config.json file")
|
||||
createAuthKey := flag.Bool("genauth", false, "Generate a new auth key and exit")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *confFilePath == "" {
|
||||
fmt.Println("Failure: --config argument is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
conf, err := config.LoadConfig(*confFilePath);
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to load: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = view.LoadTemplates()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dbctx, err := db.Open(conf.DBpath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dbctx.Close()
|
||||
|
||||
if *createAuthKey {
|
||||
key, err := dbctx.CreateNewAuthKey()
|
||||
if err != nil {
|
||||
fmt.Println("Failed to create auth key:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(key)
|
||||
return
|
||||
}
|
||||
|
||||
err = net.Start(conf)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start listen: ", err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package model
|
||||
|
||||
type File struct {
|
||||
Id, Filename, ContentType, Sha1Sum, UploadKey string
|
||||
Size, UploadTime int64
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.fuwafuwa.moe/x3/ngfshare/config"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/controller"
|
||||
"git.fuwafuwa.moe/x3/ngfshare/auth"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func Start(conf config.Config) error {
|
||||
r := mux.NewRouter()
|
||||
|
||||
authedR := r.PathPrefix("/api").Methods("POST").Subrouter()
|
||||
authedR.Use(auth.AuthMiddleware)
|
||||
authedR.HandleFunc("/upload", controller.Upload)
|
||||
authedR.HandleFunc("/delete/{id}", controller.Delete)
|
||||
|
||||
r.HandleFunc("/-{id}", controller.Download).Methods("GET")
|
||||
r.HandleFunc("/-{id}/{filename}", controller.Download).Methods("GET")
|
||||
r.HandleFunc("/-{id}/", controller.Download).Methods("GET")
|
||||
|
||||
r.HandleFunc("/", controller.WebGet).Methods("GET")
|
||||
r.HandleFunc("/login", controller.WebLogin).Methods("POST")
|
||||
r.HandleFunc("/logout", controller.WebLogout).Methods("POST")
|
||||
|
||||
http.Handle("/", r)
|
||||
|
||||
lstStr := fmt.Sprintf("%s:%d", conf.Address, conf.Port)
|
||||
fmt.Println("Listening on", lstStr)
|
||||
http.ListenAndServe(lstStr, nil)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
{{define "files"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>ngfshare - Listing</title>
|
||||
<style>
|
||||
{{template "style"}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<div id="txtheader"><h1>ngfshare</h1></div>
|
||||
<div id="upload"><form enctype="multipart/form-data" id="fupload" method="POST" action="/api/upload">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="Upload">
|
||||
</form></div>
|
||||
<div id="logout"><form id="flogout" method="POST" action="/logout">
|
||||
<input type="submit" value="Logout">
|
||||
</form></div>
|
||||
</div>
|
||||
<content>
|
||||
<h3>Files</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<thead>
|
||||
<th>Filename</th>
|
||||
<th>Size</th>
|
||||
<th>Uploaded</th>
|
||||
<th>Type</th>
|
||||
<th>Action</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td>
|
||||
<a target="_blank" href="/-{{.Id}}/{{.Filename}}"><span title="{{.Sha1Sum}}">{{.Filename}}</span></a>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{formatFileSize .Size}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{formatDate .UploadTime}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{.ContentType}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<form class="deleteform" action="/api/delete/{{.Id}}" method="POST" target="_blank">
|
||||
<input type="submit" value="Delete" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{if not .}}
|
||||
<span>No uploads yet</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
|
@ -0,0 +1,23 @@
|
|||
{{define "login"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>ngfshare - Log in</title>
|
||||
<style>
|
||||
{{template "style"}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='divLogin'>
|
||||
<h1>ngfshare</h1>
|
||||
<form id="loginForm" action="/login" method="POST">
|
||||
<!-- <label for="auth">Auth key</label>-->
|
||||
<input placeholder="Auth key" required type="password" name="auth">
|
||||
<br>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
|
@ -0,0 +1,133 @@
|
|||
{{define "style"}}
|
||||
|
||||
|
||||
body {
|
||||
background-color: #414141;
|
||||
color: #D4D3D3;
|
||||
font-size: 1.4em;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#divLogin > h1 {
|
||||
width: initial;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#divLogin {
|
||||
margin: auto;
|
||||
width: 400px;
|
||||
margin-top: 80px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #F1EBEB;
|
||||
}
|
||||
|
||||
a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
th {
|
||||
border-bottom: 1px solid #D4D3D3;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 1px dashed #D4D3D3;
|
||||
}
|
||||
|
||||
#loginForm {
|
||||
display: block;
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
#loginForm > input {
|
||||
padding: 2px 5px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
content {
|
||||
margin: auto 0px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#flogout {
|
||||
|
||||
}
|
||||
|
||||
#flogout {
|
||||
float: right;
|
||||
|
||||
}
|
||||
|
||||
#txtheader {
|
||||
width: fit-content;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#flogout {
|
||||
float: right;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#flogout input {
|
||||
width: 100px;
|
||||
background-color: #F1EBEB;
|
||||
border: none;
|
||||
color: #414141;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
content > table {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.deleteform input {
|
||||
background-color: rgba(0,0,0,0);
|
||||
border: none;
|
||||
color: #F1EBEB;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#header form input {
|
||||
width: 100px;
|
||||
background-color: #F1EBEB;
|
||||
border: none;
|
||||
color: #414141;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
#fupload {
|
||||
width: 500px;
|
||||
align-self: center;
|
||||
float: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
{{end}}
|
|
@ -0,0 +1,49 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"io"
|
||||
"html/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
var tmpl *template.Template
|
||||
|
||||
func addFuncs(t *template.Template) *template.Template {
|
||||
fmap := template.FuncMap{
|
||||
"formatDate": func(unix int64) string {
|
||||
return time.Unix(unix, 0).Format("2006-01-02 15:04:05")
|
||||
},
|
||||
"formatFileSize": func(b int64) string {
|
||||
const unit = 1024
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
|
||||
},
|
||||
}
|
||||
return t.Funcs(fmap)
|
||||
}
|
||||
|
||||
func LoadTemplates() error {
|
||||
t, err := addFuncs(template.New("")).ParseGlob("./templates/*")
|
||||
if err != nil {
|
||||
log.Println("LoadTemplates:", err)
|
||||
return err
|
||||
}
|
||||
tmpl = t
|
||||
|
||||
log.Println("Templates loaded")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Execute(tstr string, wr io.Writer, data any) error {
|
||||
return tmpl.ExecuteTemplate(wr, tstr, data)
|
||||
}
|
Loading…
Reference in New Issue