main.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package main
  2. import (
  3. "os"
  4. "log"
  5. "path"
  6. "net/url"
  7. "net/http"
  8. "io/ioutil"
  9. "strings"
  10. "encoding/json"
  11. "path/filepath"
  12. "compress/gzip"
  13. "bytes"
  14. "archive/tar"
  15. "io"
  16. "time"
  17. "math"
  18. "fmt"
  19. "html/template"
  20. )
  21. type Item struct {
  22. Name string
  23. Size int64
  24. URL string
  25. Date time.Time
  26. Valid bool
  27. }
  28. func (i Item) FDate() string {
  29. if i.Date.IsZero() {
  30. return "-"
  31. }
  32. return i.Date.Format(time.RFC822)
  33. }
  34. func (i Item) FSize() string {
  35. if i.Size == 0 {
  36. return "-"
  37. }
  38. if i.Size < 1024 {
  39. return fmt.Sprintf("%d B", i.Size)
  40. }
  41. exp := int(math.Log(float64(i.Size)) / math.Log(1024))
  42. pre := string("KMGTPE"[(exp-1)])
  43. fsize := float64(i.Size) / math.Pow(1024, float64(exp))
  44. return fmt.Sprintf("%.1f %siB", fsize, pre)
  45. }
  46. type Page struct {
  47. Title string
  48. Items []Item
  49. Error int
  50. }
  51. type Config struct {
  52. Directories map[string]Directory `json:"data"`
  53. Address string `json:"address"`
  54. }
  55. type Directory struct {
  56. Path string `json:"path"`
  57. Extension string `json:"ext"`
  58. Compress bool `json:"compress"`
  59. }
  60. var config = Config{}
  61. func main() {
  62. defer func() {
  63. if r := recover(); r != nil {
  64. log.Fatalln("Exiting due to error:", r)
  65. os.Exit(1)
  66. }
  67. }()
  68. var err error
  69. // Processing template
  70. tmpl, err = template.New("index").Parse(index)
  71. if err != nil {
  72. panic(err)
  73. }
  74. // Reading configuration
  75. logFile, err := ioutil.ReadFile("config.json")
  76. if err != nil {
  77. panic(err)
  78. }
  79. err = json.Unmarshal(logFile, &config)
  80. if err !=nil {
  81. panic(err)
  82. }
  83. // Serving HTTP
  84. http.HandleFunc("/", router)
  85. err = http.ListenAndServe(config.Address, nil)
  86. if err != nil {
  87. panic(err)
  88. }
  89. log.Println("Listening on", config.Address)
  90. }
  91. func router(w http.ResponseWriter, r *http.Request) {
  92. defer func() {
  93. if rec := recover(); rec != nil {
  94. handleError(w, r, rec, "Internal Server Error", http.StatusInternalServerError)
  95. }
  96. }()
  97. if r.URL.Path == "/" {
  98. serveIndex(w, r)
  99. return
  100. }
  101. location, _ := path.Split(r.URL.Path)
  102. if location == "/" {
  103. serveDirectory(w, r)
  104. } else {
  105. serveFile(w, r)
  106. }
  107. }
  108. func handleError(w http.ResponseWriter, r *http.Request, err interface{}, message string, status int) {
  109. log.Println("Request error [", r.URL.Path, "]:", message + ":", err)
  110. w.WriteHeader(status)
  111. tmpl.ExecuteTemplate(w, "index", &Page{Title: message, Error: status})
  112. }
  113. func serveDirectory(w http.ResponseWriter, r *http.Request) {
  114. var page Page
  115. urlPath, err := url.QueryUnescape(r.URL.Path)
  116. if err !=nil {
  117. handleError(w,r, nil, "Invalid URL", http.StatusBadRequest)
  118. return
  119. }
  120. urlPath = strings.TrimLeft(urlPath, "/")
  121. configDir, exists := config.Directories[urlPath]
  122. if !exists {
  123. handleError(w,r, nil, "Directory do not exist", http.StatusNotFound)
  124. return
  125. }
  126. files, err := ioutil.ReadDir(configDir.Path)
  127. if err != nil {
  128. handleError(w,r, err, "Unable to read server directory", http.StatusForbidden)
  129. return
  130. }
  131. page = Page{Title: urlPath, Items: make([]Item, len(files))}
  132. for i, file := range files {
  133. if file.IsDir() {
  134. continue
  135. }
  136. ext := strings.TrimLeft(filepath.Ext(file.Name()), ".")
  137. if strings.ToLower(ext) != strings.ToLower(configDir.Extension) {
  138. continue
  139. }
  140. page.Items[i] = Item{
  141. Name: file.Name(),
  142. URL: url.QueryEscape(urlPath + "/" + file.Name()),
  143. Size: file.Size(),
  144. Date: file.ModTime(),
  145. Valid: true,
  146. }
  147. }
  148. tmpl.ExecuteTemplate(w, "index", &page)
  149. }
  150. func serveFile(w http.ResponseWriter, r *http.Request) {
  151. urlPath, err := url.QueryUnescape(r.URL.Path)
  152. if err !=nil {
  153. handleError(w,r, nil, "Invalid URL", http.StatusBadRequest)
  154. return
  155. }
  156. urlPath = strings.TrimLeft(urlPath, "/")
  157. location, urlFile := path.Split(urlPath)
  158. configDir, exists := config.Directories[strings.TrimRight(location, "/")]
  159. if !exists {
  160. handleError(w,r, nil, "Directory do not exist", http.StatusNotFound)
  161. return
  162. }
  163. servedFile := path.Join(configDir.Path, urlFile)
  164. if _, err := os.Stat(servedFile); os.IsNotExist(err) {
  165. handleError(w,r, err, "File does not exist", http.StatusNotFound)
  166. return
  167. }
  168. if configDir.Compress {
  169. buf := new(bytes.Buffer)
  170. err = compress(servedFile, buf)
  171. if err != nil {
  172. handleError(w, r, err, "Unable to compress file", http.StatusInternalServerError)
  173. return
  174. }
  175. w.Header().Set("Content-Disposition", "attachment; filename=\""+urlFile+".tar.gz\"")
  176. w.Write(buf.Bytes())
  177. } else {
  178. file, err := ioutil.ReadFile(servedFile)
  179. if err != nil {
  180. handleError(w, r, err, "Unable to read file", http.StatusInternalServerError)
  181. return
  182. }
  183. w.Header().Set("Content-Disposition", "attachment; filename=\""+urlFile+"\"")
  184. w.Write(file)
  185. }
  186. }
  187. func compress(filePath string, buf *bytes.Buffer) error {
  188. gw := gzip.NewWriter(buf)
  189. defer gw.Close()
  190. tw := tar.NewWriter(gw)
  191. defer tw.Close()
  192. file, err := os.Open(filePath)
  193. if err != nil {
  194. return err
  195. }
  196. defer file.Close()
  197. if stat, err := file.Stat(); err == nil {
  198. header := new(tar.Header)
  199. header.Name = path.Base(filePath)
  200. header.Size = stat.Size()
  201. header.Mode = int64(stat.Mode())
  202. header.ModTime = stat.ModTime()
  203. if err := tw.WriteHeader(header); err != nil {
  204. return err
  205. }
  206. if _, err := io.Copy(tw, file); err != nil {
  207. return err
  208. }
  209. }
  210. return nil
  211. }
  212. func serveIndex(w http.ResponseWriter, r *http.Request) {
  213. page := Page{Title: "Index", Items: make([]Item, len(config.Directories))}
  214. i := 0
  215. for name, dir := range config.Directories {
  216. stat, err := os.Stat(dir.Path)
  217. if err == nil {
  218. page.Items[i].Date = stat.ModTime()
  219. }
  220. page.Items[i].Name = name
  221. page.Items[i].URL = url.QueryEscape(name)
  222. page.Items[i].Valid = true
  223. i++
  224. }
  225. tmpl.ExecuteTemplate(w, "index", &page)
  226. }