Advertisement
Guest User

Untitled

a guest
May 25th, 2018
89
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 14.45 KB | None | 0 0
  1. package main
  2.  
  3. import (
  4.     "archive/zip"
  5.     "flag"
  6.     "html/template"
  7.     "io"
  8.     "io/ioutil"
  9.     "log"
  10.     "net/http"
  11.     "os"
  12.     "path"
  13.     "path/filepath"
  14.     "strings"
  15.     "sync"
  16.  
  17.     "github.com/gorilla/handlers"
  18.  
  19.     "github.com/gorilla/mux"
  20. )
  21.  
  22. var (
  23.     address                     = flag.String("a", "", "ip to bind to")
  24.     port                        = flag.String("p", "80", "port server runs on")
  25.     directory                   = flag.String("d", "public", "directory to serve files from")
  26.     useBrowsePassword           = flag.Bool("use-browse-pass", false, "use a password for browsing")
  27.     usePassword                 = flag.Bool("use-pass", false, "use the password or not")
  28.     password                    = flag.String("pass", "", "password required to enter the website")
  29.     passwordField               = flag.String("pass-field", "password", "name of the password cookie")
  30.     browsePassword              = flag.String("browse-pass", "", "password required to browse files")
  31.     browsePasswordIncorrectFile = flag.String("browse-pass-fail", "", "page to serve if browse password fails")
  32.     fileNotFoundPage            = flag.String("404", "", "file to serve if a page is not found")
  33.     passwordIncorrectFile       = flag.String("pass-fail", "", "file to serve if the password fails")
  34.     browseTemplate              = flag.String("browse-template", "", "template file for the browse page")
  35.     viewTemplate                = flag.String("view-template", "", "view template")
  36.     hotReload                   = flag.Bool("hot-reload", false, "hot reload template files")
  37.     faviconPath                 = flag.String("favicon", "", "favicon file")
  38.     cssFile                     = flag.String("css", "", "custom css file")
  39.  
  40.     upgradeServerPort = flag.String("upgrade-server-port", "80", "port to host the upgrade server on")
  41.     upgradeServer     = flag.Bool("host-upgrade-server", false, "host a server on port 80 that redirects to https")
  42.     key               = flag.String("key", "", "key file for ssl")
  43.     cert              = flag.String("cert", "", "cert file for ssl")
  44.     useSSL            = flag.Bool("use-ssl", false, "specifies if to use ssl or not, requires key and cert to not be empty")
  45. )
  46.  
  47. var css = `
  48. body,
  49. html {
  50.     margin: 0px;
  51.     padding: 0px;
  52. }
  53.  
  54. a {
  55.     color: white;
  56.     font-family: roboto;
  57. }
  58.  
  59. audio,
  60. video,
  61. img {
  62.     width: 100%;
  63.     max-width: 500px;
  64. }
  65.  
  66. tr a:hover,
  67. tr:nth-child(odd) a:hover,
  68. a:hover {
  69.     color: #aaaaff;
  70. }
  71.  
  72. tr:nth-child(odd) a {
  73.     color: #cccccc;
  74. }
  75.  
  76. tr:nth-child(odd) {
  77.     background-color: #323232;
  78. }
  79.  
  80. tr:hover {
  81.     background-color: #585858;
  82. }
  83.  
  84. tr.selected {
  85.     background-color: #5c633d;
  86. }
  87.  
  88. td {
  89.     padding-bottom: 5px;
  90.     padding-right: 50px;
  91. }
  92.  
  93. body {
  94.     background-color: #232323;
  95. }
  96.  
  97. h1 {
  98.     color: white;
  99. }
  100.  
  101. .dirlink {
  102.     margin: 0px;
  103. }
  104.  
  105. .header {
  106.     overflow: hidden;
  107.     width: 100%;
  108.     margin: 0;
  109.     padding-left: 10px;
  110.     background-color: #1d1d1d;
  111.     position: fixed;
  112.     top: 0px;
  113.     left: 0px;
  114. }
  115.  
  116. .content {
  117.     margin: 30px 0px 0px 10px;
  118.     overflow: hidden;
  119. }
  120. `
  121.  
  122. var tmplBrowseMu sync.RWMutex
  123. var tmplBrowse = template.Must(template.New("").Parse(
  124.     `
  125. <html>
  126.  
  127. <head>
  128.     <style>
  129.         {{.CSS}}
  130.     </style>
  131. </head>
  132.  
  133. <body>
  134.  
  135.     <div class="header">
  136.         <!-- Directory list -->
  137.         <a class="dirlink" href="/f/">/</a>
  138.         {{- range .Dirlinks }}
  139.         <a class="dirlink" href="/f/{{.Path}}">{{.Name}}</a>
  140.         {{- end }}
  141.     </div>
  142.  
  143.     <div class="content">
  144.  
  145.  
  146.         <table>
  147.  
  148.             <!-- Ascend directory -->
  149.             <tr>
  150.                 <td>
  151.                     <a href="..">../</a>
  152.                 </td>
  153.             </tr>
  154.  
  155.             <!-- Current directory -->
  156.             <tr>
  157.                 <td>
  158.                     <a href=".">.</a>
  159.                 </td>
  160.                 <td>
  161.                     <a href="/z/{{.Rpath}}.zip">DOWNLOAD</a>
  162.                 </td>
  163.             </tr>
  164.  
  165.             <!-- Files -->
  166.             {{- range .Directories }}
  167.             <tr>
  168.                 <td>
  169.                     <a href="/f/{{$.Path}}{{.}}/">{{.}}/</a>
  170.                 </td>
  171.                 <td>
  172.                     <a href="/z/{{$.Path}}{{.}}.zip">DOWNLOAD</a>
  173.                 </td>
  174.             </tr>
  175.             {{- end }} {{- range .Files }}
  176.             <tr>
  177.                 <td>
  178.                     <a href="/v/{{$.Path}}{{.}}">{{.}}</a>
  179.                 </td>
  180.                 <td>
  181.                     <a href="/d/{{$.Path}}{{.}}">DOWNLOAD</a>
  182.                 </td>
  183.                 <td>
  184.                     <a href="/f/{{$.Path}}{{.}}">OPEN</a>
  185.                 </td>
  186.             </tr>
  187.             {{- end }}
  188.         </table>
  189.     </div>
  190. </body>
  191.  
  192. </html>
  193. `))
  194.  
  195. const (
  196.     ftypeImage = "image"
  197.     ftypeVideo = "video"
  198.     ftypeAudio = "audio"
  199. )
  200.  
  201. var filetypes = map[string]string{
  202.     ".mp3":  ftypeAudio,
  203.     ".wav":  ftypeAudio,
  204.     ".flac": ftypeAudio,
  205.  
  206.     ".png":  ftypeImage,
  207.     ".jpeg": ftypeImage,
  208.     ".jpg":  ftypeImage,
  209.     ".gif":  ftypeImage,
  210.     ".bmp":  ftypeImage,
  211.     ".svg":  ftypeImage,
  212.  
  213.     ".mp4":  ftypeVideo,
  214.     ".webm": ftypeVideo,
  215.     ".ogg":  ftypeVideo,
  216. }
  217.  
  218. var tmplFuncs = template.FuncMap{
  219.     "Filetype": func(p string) string {
  220.         if v, ok := filetypes[strings.ToLower(path.Ext(p))]; ok {
  221.             return v
  222.         }
  223.         return ""
  224.     },
  225. }
  226. var tmplViewMu sync.RWMutex
  227. var tmplView = template.Must(template.New("").Funcs(tmplFuncs).Parse(
  228.     `
  229. <html>
  230.  
  231. <head>
  232.     <style>
  233.         {{.CSS}}       
  234.     </style>
  235. </head>
  236.  
  237. <body>
  238.  
  239.     <!-- Directory list -->
  240.     <div class="header">
  241.         <a class="dirlink" href="/f/">/</a>
  242.         {{- range .Dirlinks }}
  243.         <a class="dirlink" href="/f/{{.Path}}">{{.Name}}</a>
  244.         {{- end }}
  245.     </div>
  246.  
  247.     <div class="content">
  248.         {{- if .Path | Filetype | eq "image" }}
  249.         <img src="/f/{{.Path}}"> {{- end }}
  250.  
  251.         {{- if .Path | Filetype | eq "video" }}
  252.         <video src="/f/{{.Path}}"></video>
  253.         {{- end }}
  254.  
  255.         {{- if .Path | Filetype | eq "audio" }}
  256.         <audio autoplay controls src="/f/{{.Path}}"></audio>
  257.         {{- end }}
  258.  
  259.         {{- if .Path | Filetype | eq "" }}
  260.         <h1> Cannot view file </h1>
  261.         {{- end }}
  262.  
  263.         <table>
  264.                 <!-- Ascend directory -->
  265.                 <tr>
  266.                     <td>
  267.                         <a href="/f/{{.Dir}}../">../</a>
  268.                     </td>
  269.                 </tr>
  270.  
  271.                 <!-- Current directory -->
  272.                 <tr>
  273.                     <td>
  274.                         <a href="/f/{{.Dir}}">.</a>
  275.                     </td>
  276.                     <td>
  277.                         <a href="/z/{{.Dir}}.zip">DOWNLOAD</a>
  278.                     </td>
  279.                 </tr>
  280.  
  281.                 <!-- Files -->
  282.                 {{- range .Directories }}
  283.                 <tr>
  284.                     <td>
  285.                         <a href="/f/{{$.Dir}}{{.}}/">{{.}}/</a>
  286.                     </td>
  287.                     <td>
  288.                         <a href="/z/{{$.Dir}}{{.}}.zip">DOWNLOAD</a>
  289.                     </td>
  290.                 </tr>
  291.                 {{- end }} {{- range .Files }}
  292.                 <tr {{if eq . $.File }}class="selected"{{ end }}>
  293.                     <td>
  294.                         <a href="/v/{{$.Dir}}{{.}}">{{.}}</a>
  295.                     </td>
  296.                     <td>
  297.                         <a href="/d/{{$.Dir}}{{.}}">DOWNLOAD</a>
  298.                     </td>
  299.                     <td>
  300.                         <a href="/f/{{$.Dir}}{{.}}">OPEN</a>
  301.                     </td>
  302.                 </tr>
  303.                 {{- end }}
  304.             </table>
  305.     </div>
  306. </body>
  307.  
  308. </html>
  309. `))
  310.  
  311. func parsePasswords(pass string) []string {
  312.     return strings.Split(pass, ",")
  313. }
  314.  
  315. func containsPassword(pass string, value string) bool {
  316.     for _, v := range parsePasswords(pass) {
  317.         if v == value {
  318.             return true
  319.         }
  320.     }
  321.  
  322.     return false
  323. }
  324.  
  325. func sendError(writer http.ResponseWriter, err string, code int) {
  326.     log.Println(err)
  327.     http.Error(writer, err, code)
  328. }
  329.  
  330. type link struct {
  331.     Name string
  332.     Path string
  333. }
  334.  
  335. // createLinks creates the directory links at the top of the page
  336. func createLinks(p string) []link {
  337.     dirlinks := []link{}
  338.     elems := strings.Split(path.Clean(p), "/")
  339.     for i := range elems {
  340.         dirlinks = append(
  341.             dirlinks,
  342.             link{
  343.                 Name: elems[i],
  344.                 Path: strings.Join(elems[:i+1], "/") + "/",
  345.             },
  346.         )
  347.     }
  348.     return dirlinks
  349. }
  350.  
  351. func readDir(fpath string) ([]string, []string, error) {
  352.     directories, files := []string{}, []string{}
  353.  
  354.     dir, err := os.Open(fpath)
  355.     if err != nil {
  356.         return nil, nil, err
  357.     }
  358.  
  359.     fs, err := dir.Readdir(-1)
  360.     if err != nil {
  361.         return nil, nil, err
  362.     }
  363.  
  364.     for _, v := range fs {
  365.         if v.IsDir() || v.Mode()&os.ModeSymlink != 0 {
  366.             directories = append(directories, v.Name())
  367.         } else {
  368.             files = append(files, v.Name())
  369.         }
  370.     }
  371.  
  372.     return directories, files, nil
  373. }
  374.  
  375. func browseHandler(w http.ResponseWriter, r *http.Request) {
  376.     var (
  377.         rpath       = mux.Vars(r)["path"]
  378.         fpath       = path.Join(*directory, path.Clean(rpath))
  379.         directories = []string{}
  380.         files       = []string{}
  381.     )
  382.  
  383.     f, err := os.Stat(fpath)
  384.     if err != nil {
  385.         if *fileNotFoundPage != "" {
  386.             http.ServeFile(w, r, *fileNotFoundPage)
  387.             return
  388.         }
  389.         sendError(w, "File not found: "+err.Error(), 404)
  390.         return
  391.     }
  392.  
  393.     if !f.IsDir() {
  394.         http.ServeFile(w, r, fpath)
  395.         return
  396.     }
  397.  
  398.     if *useBrowsePassword && *browsePassword != "" {
  399.         if cookie, err := r.Cookie(*passwordField); err != nil || !containsPassword(cookie.Value, *browsePassword) {
  400.             if *browsePasswordIncorrectFile != "" {
  401.                 http.ServeFile(w, r, *browsePasswordIncorrectFile)
  402.                 return
  403.             }
  404.             if *passwordIncorrectFile != "" {
  405.                 http.ServeFile(w, r, *passwordIncorrectFile)
  406.                 return
  407.             }
  408.         }
  409.     }
  410.  
  411.     directories, files, err = readDir(fpath)
  412.     if err != nil {
  413.         sendError(w, "error reading directory", http.StatusInternalServerError)
  414.         return
  415.     }
  416.  
  417.     if *hotReload {
  418.         parseTemplates()
  419.     }
  420.  
  421.     tmplBrowseMu.RLock()
  422.     err = tmplBrowse.Execute(w, map[string]interface{}{
  423.         "Directories": directories,
  424.         "Files":       files,
  425.         "Path":        path.Clean(rpath) + "/",
  426.         "Rpath":       rpath,
  427.         "Base":        path.Base(rpath),
  428.         "Dirlinks":    createLinks(rpath),
  429.         "CSS":         template.HTML("<style>" + css + "</style>"),
  430.     })
  431.     tmplBrowseMu.RUnlock()
  432.  
  433.     if err != nil {
  434.         sendError(w, "Error executing template: "+err.Error(), http.StatusInternalServerError)
  435.         return
  436.     }
  437. }
  438.  
  439. func downloadHandler(trimZip bool) func(w http.ResponseWriter, r *http.Request) {
  440.     return func(w http.ResponseWriter, r *http.Request) {
  441.         var (
  442.             fpath = path.Join(*directory, path.Clean(mux.Vars(r)["path"]))
  443.         )
  444.  
  445.         if trimZip {
  446.             fpath = strings.TrimSuffix(fpath, ".zip")
  447.         }
  448.  
  449.         stat, err := os.Stat(fpath)
  450.         if err != nil {
  451.             sendError(w, "Error getting stats: "+err.Error(), http.StatusInternalServerError)
  452.             return
  453.         }
  454.  
  455.         w.Header().Set("Content-Disposition", "attachment")
  456.  
  457.         if stat.IsDir() || stat.Mode()&os.ModeSymlink != 0 {
  458.  
  459.             if strings.HasSuffix(mux.Vars(r)["path"], "/.zip") {
  460.                 filename := "content.zip"
  461.                 w.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
  462.             }
  463.  
  464.             err := archive(fpath, w)
  465.             if err != nil {
  466.                 log.Println(err)
  467.                 return
  468.             }
  469.         } else {
  470.             http.ServeFile(w, r, fpath)
  471.         }
  472.     }
  473. }
  474.  
  475. func viewHandler(w http.ResponseWriter, r *http.Request) {
  476.     var (
  477.         rpath = mux.Vars(r)["path"]
  478.         fpath = path.Join(*directory, path.Clean(path.Dir(rpath)))
  479.     )
  480.  
  481.     if *hotReload {
  482.         parseTemplates()
  483.     }
  484.  
  485.     stat, err := os.Stat(path.Join(*directory, path.Clean(rpath)))
  486.     if err != nil || stat.IsDir() {
  487.         http.ServeFile(w, r, *fileNotFoundPage)
  488.         return
  489.     }
  490.  
  491.     directories, files, err := readDir(fpath)
  492.     if err != nil {
  493.         sendError(w, "error reading directory", http.StatusInternalServerError)
  494.         return
  495.     }
  496.  
  497.     tmplViewMu.RLock()
  498.     err = tmplView.Execute(w, map[string]interface{}{
  499.         "Dirlinks":    createLinks(rpath),
  500.         "Path":        path.Clean(rpath),
  501.         "Directories": directories,
  502.         "Files":       files,
  503.         "Dir":         path.Dir(rpath) + "/",
  504.         "File":        path.Base(rpath),
  505.         "CSS":         template.HTML("<style>" + css + "</style>"),
  506.     })
  507.     if err != nil {
  508.         log.Println("Error executing view template: ", err)
  509.     }
  510.     tmplViewMu.RUnlock()
  511. }
  512.  
  513. func archive(inFilePath string, writer io.Writer) error {
  514.     zipWriter := zip.NewWriter(writer)
  515.  
  516.     basePath := filepath.Dir(inFilePath)
  517.  
  518.     err := filepath.Walk(inFilePath, func(filePath string, fileInfo os.FileInfo, err error) error {
  519.         if err != nil || fileInfo.IsDir() {
  520.             return err
  521.         }
  522.  
  523.         relativeFilePath, err := filepath.Rel(basePath, filePath)
  524.         if err != nil {
  525.             return err
  526.         }
  527.  
  528.         archivePath := path.Join(filepath.SplitList(relativeFilePath)...)
  529.  
  530.         file, err := os.Open(filePath)
  531.         if err != nil {
  532.             return err
  533.         }
  534.         defer func() {
  535.             _ = file.Close()
  536.         }()
  537.  
  538.         zipFileWriter, err := zipWriter.Create(archivePath)
  539.         if err != nil {
  540.             return err
  541.         }
  542.  
  543.         _, err = io.Copy(zipFileWriter, file)
  544.         return err
  545.     })
  546.     if err != nil {
  547.         return err
  548.     }
  549.  
  550.     return zipWriter.Close()
  551. }
  552.  
  553. func passwordMiddleware(h http.Handler) http.Handler {
  554.     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  555.         if c, err := r.Cookie(*passwordField); err == nil && containsPassword(c.Value, *password) {
  556.             h.ServeHTTP(w, r)
  557.             return
  558.         }
  559.  
  560.         http.ServeFile(w, r, *passwordIncorrectFile)
  561.     })
  562. }
  563.  
  564. func parseTemplates() {
  565.     tmplBrowseMu.Lock()
  566.     defer tmplBrowseMu.Unlock()
  567.  
  568.     tmplViewMu.Lock()
  569.     defer tmplViewMu.Unlock()
  570.  
  571.     if *viewTemplate != "" {
  572.         v, err := template.New("view.html").Funcs(tmplFuncs).ParseFiles(*viewTemplate)
  573.         if err != nil {
  574.             log.Println(err)
  575.             return
  576.         }
  577.         tmplView = v
  578.     }
  579.     if *browseTemplate != "" {
  580.         t, err := template.ParseFiles(*browseTemplate)
  581.         if err != nil {
  582.             log.Println(err)
  583.             return
  584.         }
  585.         tmplBrowse = t
  586.     }
  587.     if *cssFile != "" {
  588.         if t, err := ioutil.ReadFile(*cssFile); err == nil {
  589.             css = string(t)
  590.         } else {
  591.             log.Println("error loading CSS: ", err)
  592.         }
  593.     }
  594.  
  595. }
  596.  
  597. func fileHandler(path string) func(w http.ResponseWriter, r *http.Request) {
  598.     return func(w http.ResponseWriter, r *http.Request) {
  599.         http.ServeFile(w, r, path)
  600.     }
  601. }
  602.  
  603. func main() {
  604.     flag.Parse()
  605.  
  606.     if *browseTemplate != "" || *viewTemplate != "" || *cssFile != "" {
  607.         log.Println("parsing templates")
  608.         parseTemplates()
  609.     }
  610.  
  611.     root := mux.NewRouter()
  612.  
  613.     // Public routes
  614.     if *faviconPath != "" {
  615.         root.HandleFunc("/favicon", fileHandler(*faviconPath))
  616.     }
  617.  
  618.     // Password locked routes
  619.     func(r *mux.Router) {
  620.         if *usePassword && *password != "" {
  621.             r.Use(passwordMiddleware)
  622.         }
  623.  
  624.         r.HandleFunc("/f/{path:.*}", browseHandler)
  625.         r.HandleFunc("/d/{path:.*}", downloadHandler(false))
  626.         r.HandleFunc("/z/{path:.*}", downloadHandler(true))
  627.         r.HandleFunc("/v/{path:.*}", viewHandler)
  628.         r.PathPrefix("/").Handler(http.RedirectHandler("/f/", 302))
  629.  
  630.     }(root.PathPrefix("/").Subrouter())
  631.  
  632.     log.Println("Server listening on " + *port)
  633.     var err error
  634.     if *key != "" && *cert != "" && *useSSL {
  635.  
  636.         // Start upgrade server. Upgrades requests from http to https via redirect
  637.         if *upgradeServer {
  638.             log.Println("Hosting upgrade server on port 80")
  639.             go func() {
  640.                 err := http.ListenAndServe(*address+":"+*upgradeServerPort, handlers.LoggingHandler(os.Stdout, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  641.                     http.Redirect(w, req, "https://"+req.Host+req.URL.String(), 302)
  642.                 })))
  643.                 if err != nil {
  644.                     log.Println("Error starting upgrade server")
  645.                     return
  646.                 }
  647.             }()
  648.         }
  649.  
  650.         err = http.ListenAndServeTLS(*address+":"+*port, *cert, *key, handlers.LoggingHandler(os.Stdout, root))
  651.         if err != nil {
  652.             log.Fatal(err)
  653.         }
  654.  
  655.     } else {
  656.         err = http.ListenAndServe(*address+":"+*port, handlers.LoggingHandler(os.Stdout, root))
  657.         if err != nil {
  658.             log.Fatal(err)
  659.         }
  660.     }
  661. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement