261 lines
5.9 KiB
Go
261 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
type Config struct {
|
|
Listen string `json:"listen"`
|
|
ApiKey string `json:"api_key"`
|
|
DeleteKey string `json:"delete_key"`
|
|
MaxFileSize int64 `json:"maximum_file_size"`
|
|
|
|
Path struct {
|
|
I string `json:"i"`
|
|
Client string `json:"client"`
|
|
} `json:"path"`
|
|
|
|
Http struct {
|
|
Enabled bool `json:"enabled"`
|
|
Listen string `json:"listen"`
|
|
} `json:"http"`
|
|
|
|
Https struct {
|
|
Enabled bool `json:"enabled"`
|
|
Listen string `json:"listen"`
|
|
Cert string `json:"cert"`
|
|
Key string `json:"key"`
|
|
} `json:"https"`
|
|
|
|
CfCacheInvalidate struct {
|
|
Enabled bool `json:"enabled"`
|
|
Token string `json:"token"`
|
|
Email string `json:"email"`
|
|
Domain string `json:"domain"`
|
|
Url string `json:"url"`
|
|
} `json:"cloudflare-cache-invalidate"`
|
|
}
|
|
|
|
var config Config
|
|
|
|
type ErrorMessage struct {
|
|
Error string `json:"error"`
|
|
Code int `json:"code"`
|
|
}
|
|
|
|
type SuccessMessage struct {
|
|
Delkey string `json:"delkey"`
|
|
}
|
|
|
|
func readConfig(name string) Config {
|
|
file, _ := os.Open(name)
|
|
decoder := json.NewDecoder(file)
|
|
config := Config{}
|
|
err := decoder.Decode(&config)
|
|
if err != nil {
|
|
fmt.Println("Error reading config: ", err)
|
|
}
|
|
return config
|
|
}
|
|
|
|
func validateConfig(config Config) {
|
|
if !config.Http.Enabled && !config.Https.Enabled {
|
|
log.Fatal("At least one of http or https must be enabled!")
|
|
}
|
|
if len(config.ApiKey) == 0 {
|
|
log.Fatal("A static key must be defined in the configuration!")
|
|
}
|
|
if len(config.DeleteKey) == 0 {
|
|
log.Fatal("A static delete key must be defined in the configuration!")
|
|
}
|
|
if len(config.Path.I) == 0 {
|
|
config.Path.I = "../i"
|
|
}
|
|
if len(config.Path.Client) == 0 {
|
|
config.Path.Client = "../client"
|
|
}
|
|
}
|
|
|
|
func makeDelkey(ident string) string {
|
|
key := []byte(config.DeleteKey)
|
|
h := hmac.New(sha256.New, key)
|
|
h.Write([]byte(ident))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
func index(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/" {
|
|
http.ServeFile(w, r, filepath.Join(config.Path.Client, "index.html"))
|
|
} else {
|
|
http.ServeFile(w, r, filepath.Join(config.Path.Client, r.URL.Path[1:]))
|
|
}
|
|
}
|
|
|
|
func upload(w http.ResponseWriter, r *http.Request) {
|
|
if r.ContentLength > config.MaxFileSize {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: "File size too large", Code: 1})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
r.ParseMultipartForm(50000000)
|
|
file, _, err := r.FormFile("file")
|
|
|
|
if err != nil {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 5})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
apikey := r.FormValue("api_key")
|
|
if apikey != config.ApiKey {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: "API key doesn't match", Code: 2})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
ident := r.FormValue("ident")
|
|
if len(ident) != 22 {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: "Ident filename length is incorrect", Code: 3})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
identPath := path.Join(config.Path.I, path.Base(ident))
|
|
if _, err := os.Stat(identPath); err == nil {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: "Ident is already taken.", Code: 4})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
out, err := os.Create(identPath)
|
|
if err != nil {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 6})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
defer out.Close()
|
|
|
|
out.Write([]byte{'U', 'P', '1', 0})
|
|
_, err = io.Copy(out, file)
|
|
if err != nil {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 7})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
delkey := makeDelkey(ident)
|
|
|
|
result, err := json.Marshal(&SuccessMessage{Delkey: delkey})
|
|
if err != nil {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 8})
|
|
w.Write(msg)
|
|
}
|
|
w.Write(result)
|
|
}
|
|
|
|
func delfile(w http.ResponseWriter, r *http.Request) {
|
|
ident := r.FormValue("ident")
|
|
delkey := r.FormValue("delkey")
|
|
|
|
if len(ident) != 22 {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: "Ident filename length is incorrect", Code: 3})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
identPath := path.Join(config.Path.I, ident)
|
|
if _, err := os.Stat(identPath); os.IsNotExist(err) {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: "Ident does not exist.", Code: 9})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
if delkey != makeDelkey(ident) {
|
|
msg, _ := json.Marshal(&ErrorMessage{Error: "Incorrect delete key", Code: 10})
|
|
w.Write(msg)
|
|
return
|
|
}
|
|
|
|
if config.CfCacheInvalidate.Enabled {
|
|
if config.Http.Enabled {
|
|
cfInvalidate(ident, false)
|
|
}
|
|
if config.Https.Enabled {
|
|
cfInvalidate(ident, true)
|
|
}
|
|
}
|
|
|
|
os.Remove(identPath)
|
|
http.Redirect(w, r, "/", 301)
|
|
}
|
|
|
|
func cfInvalidate(ident string, https bool) {
|
|
var invUrl string
|
|
if https {
|
|
invUrl = "https://" + config.CfCacheInvalidate.Url
|
|
} else {
|
|
invUrl = "http://" + config.CfCacheInvalidate.Url
|
|
}
|
|
invUrl += "/i/" + ident
|
|
|
|
if _, err := http.PostForm("https://www.cloudflare.com/api_json.html", url.Values{
|
|
"a": {"zone_file_purge"},
|
|
"tkn": {config.CfCacheInvalidate.Token},
|
|
"email": {config.CfCacheInvalidate.Email},
|
|
"z": {config.CfCacheInvalidate.Domain},
|
|
"url": {invUrl},
|
|
}); err != nil {
|
|
log.Printf("Cache invalidate failed for '%s': '%s'", ident, err.Error())
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
configName := flag.String("config", "server.conf", "Configuration file")
|
|
flag.Parse()
|
|
|
|
config = readConfig(*configName)
|
|
validateConfig(config)
|
|
|
|
http.Handle("/i/", http.StripPrefix("/i", http.FileServer(http.Dir(config.Path.I))))
|
|
http.HandleFunc("/up", upload)
|
|
http.HandleFunc("/del", delfile)
|
|
http.HandleFunc("/", index)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
if config.Http.Enabled {
|
|
log.Printf("Starting HTTP server on %s\n", config.Http.Listen)
|
|
log.Println(http.ListenAndServe(config.Http.Listen, nil))
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
if config.Https.Enabled {
|
|
log.Printf("Starting HTTPS server on %s\n", config.Https.Listen)
|
|
log.Println(http.ListenAndServeTLS(config.Https.Listen, config.Https.Cert, config.Https.Key, nil))
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
}
|