server.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package main
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "encoding/json"
  7. "flag"
  8. "fmt"
  9. "io"
  10. "log"
  11. "net/http"
  12. "net/url"
  13. "os"
  14. "path"
  15. "path/filepath"
  16. "sync"
  17. )
  18. type Config struct {
  19. Listen string `json:"listen"`
  20. ApiKey string `json:"api_key"`
  21. DeleteKey string `json:"delete_key"`
  22. MaxFileSize int64 `json:"maximum_file_size"`
  23. Path struct {
  24. I string `json:"i"`
  25. Client string `json:"client"`
  26. } `json:"path"`
  27. Http struct {
  28. Enabled bool `json:"enabled"`
  29. Listen string `json:"listen"`
  30. } `json:"http"`
  31. Https struct {
  32. Enabled bool `json:"enabled"`
  33. Listen string `json:"listen"`
  34. Cert string `json:"cert"`
  35. Key string `json:"key"`
  36. } `json:"https"`
  37. CfCacheInvalidate struct {
  38. Enabled bool `json:"enabled"`
  39. Token string `json:"token"`
  40. Email string `json:"email"`
  41. Domain string `json:"domain"`
  42. Url string `json:"url"`
  43. } `json:"cloudflare-cache-invalidate"`
  44. }
  45. var config Config
  46. type ErrorMessage struct {
  47. Error string `json:"error"`
  48. Code int `json:"code"`
  49. }
  50. type SuccessMessage struct {
  51. Delkey string `json:"delkey"`
  52. }
  53. func readConfig(name string) Config {
  54. file, _ := os.Open(name)
  55. decoder := json.NewDecoder(file)
  56. config := Config{}
  57. err := decoder.Decode(&config)
  58. if err != nil {
  59. fmt.Println("Error reading config: ", err)
  60. }
  61. return config
  62. }
  63. func validateConfig(config Config) {
  64. if !config.Http.Enabled && !config.Https.Enabled {
  65. log.Fatal("At least one of http or https must be enabled!")
  66. }
  67. if len(config.ApiKey) == 0 {
  68. log.Fatal("A static key must be defined in the configuration!")
  69. }
  70. if len(config.DeleteKey) == 0 {
  71. log.Fatal("A static delete key must be defined in the configuration!")
  72. }
  73. if len(config.Path.I) == 0 {
  74. config.Path.I = "../i"
  75. }
  76. if len(config.Path.Client) == 0 {
  77. config.Path.Client = "../client"
  78. }
  79. }
  80. func makeDelkey(ident string) string {
  81. key := []byte(config.DeleteKey)
  82. h := hmac.New(sha256.New, key)
  83. h.Write([]byte(ident))
  84. return hex.EncodeToString(h.Sum(nil))
  85. }
  86. func index(w http.ResponseWriter, r *http.Request) {
  87. if r.URL.Path == "/" {
  88. http.ServeFile(w, r, filepath.Join(config.Path.Client, "index.html"))
  89. } else {
  90. http.ServeFile(w, r, filepath.Join(config.Path.Client, r.URL.Path[1:]))
  91. }
  92. }
  93. func upload(w http.ResponseWriter, r *http.Request) {
  94. if r.ContentLength > config.MaxFileSize {
  95. msg, _ := json.Marshal(&ErrorMessage{Error: "File size too large", Code: 1})
  96. w.Write(msg)
  97. return
  98. }
  99. r.ParseMultipartForm(50000000)
  100. file, _, err := r.FormFile("file")
  101. if err != nil {
  102. msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 5})
  103. w.Write(msg)
  104. return
  105. }
  106. defer file.Close()
  107. apikey := r.FormValue("api_key")
  108. if apikey != config.ApiKey {
  109. msg, _ := json.Marshal(&ErrorMessage{Error: "API key doesn't match", Code: 2})
  110. w.Write(msg)
  111. return
  112. }
  113. ident := r.FormValue("ident")
  114. if len(ident) != 22 {
  115. msg, _ := json.Marshal(&ErrorMessage{Error: "Ident filename length is incorrect", Code: 3})
  116. w.Write(msg)
  117. return
  118. }
  119. identPath := path.Join(config.Path.I, path.Base(ident))
  120. if _, err := os.Stat(identPath); err == nil {
  121. msg, _ := json.Marshal(&ErrorMessage{Error: "Ident is already taken.", Code: 4})
  122. w.Write(msg)
  123. return
  124. }
  125. out, err := os.Create(identPath)
  126. if err != nil {
  127. msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 6})
  128. w.Write(msg)
  129. return
  130. }
  131. defer out.Close()
  132. out.Write([]byte{'U', 'P', '1', 0})
  133. _, err = io.Copy(out, file)
  134. if err != nil {
  135. msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 7})
  136. w.Write(msg)
  137. return
  138. }
  139. delkey := makeDelkey(ident)
  140. result, err := json.Marshal(&SuccessMessage{Delkey: delkey})
  141. if err != nil {
  142. msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 8})
  143. w.Write(msg)
  144. }
  145. w.Write(result)
  146. }
  147. func delfile(w http.ResponseWriter, r *http.Request) {
  148. ident := r.FormValue("ident")
  149. delkey := r.FormValue("delkey")
  150. if len(ident) != 22 {
  151. msg, _ := json.Marshal(&ErrorMessage{Error: "Ident filename length is incorrect", Code: 3})
  152. w.Write(msg)
  153. return
  154. }
  155. identPath := path.Join(config.Path.I, ident)
  156. if _, err := os.Stat(identPath); os.IsNotExist(err) {
  157. msg, _ := json.Marshal(&ErrorMessage{Error: "Ident does not exist.", Code: 9})
  158. w.Write(msg)
  159. return
  160. }
  161. if delkey != makeDelkey(ident) {
  162. msg, _ := json.Marshal(&ErrorMessage{Error: "Incorrect delete key", Code: 10})
  163. w.Write(msg)
  164. return
  165. }
  166. if config.CfCacheInvalidate.Enabled {
  167. if config.Http.Enabled {
  168. cfInvalidate(ident, false)
  169. }
  170. if config.Https.Enabled {
  171. cfInvalidate(ident, true)
  172. }
  173. }
  174. os.Remove(identPath)
  175. http.Redirect(w, r, "/", 301)
  176. }
  177. func cfInvalidate(ident string, https bool) {
  178. var invUrl string
  179. if https {
  180. invUrl = "https://" + config.CfCacheInvalidate.Url
  181. } else {
  182. invUrl = "http://" + config.CfCacheInvalidate.Url
  183. }
  184. invUrl += "/i/" + ident
  185. if _, err := http.PostForm("https://www.cloudflare.com/api_json.html", url.Values{
  186. "a": {"zone_file_purge"},
  187. "tkn": {config.CfCacheInvalidate.Token},
  188. "email": {config.CfCacheInvalidate.Email},
  189. "z": {config.CfCacheInvalidate.Domain},
  190. "url": {invUrl},
  191. }); err != nil {
  192. log.Printf("Cache invalidate failed for '%s': '%s'", ident, err.Error())
  193. }
  194. }
  195. func main() {
  196. configName := flag.String("config", "server.conf", "Configuration file")
  197. flag.Parse()
  198. config = readConfig(*configName)
  199. validateConfig(config)
  200. http.Handle("/i/", http.StripPrefix("/i", http.FileServer(http.Dir(config.Path.I))))
  201. http.HandleFunc("/up", upload)
  202. http.HandleFunc("/del", delfile)
  203. http.HandleFunc("/", index)
  204. var wg sync.WaitGroup
  205. wg.Add(2)
  206. go func() {
  207. defer wg.Done()
  208. if config.Http.Enabled {
  209. log.Printf("Starting HTTP server on %s\n", config.Http.Listen)
  210. log.Println(http.ListenAndServe(config.Http.Listen, nil))
  211. }
  212. }()
  213. go func() {
  214. defer wg.Done()
  215. if config.Https.Enabled {
  216. log.Printf("Starting HTTPS server on %s\n", config.Https.Listen)
  217. log.Println(http.ListenAndServeTLS(config.Https.Listen, config.Https.Cert, config.Https.Key, nil))
  218. }
  219. }()
  220. wg.Wait()
  221. }