PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/main.go

https://bitbucket.org/kardianos/staticserv
Go | 499 lines | 430 code | 50 blank | 19 comment | 59 complexity | 35ae371392d939a576d97c6f012b7b41 MD5 | raw file
Possible License(s): BSD-2-Clause
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "flag"
  6. "fmt"
  7. "io"
  8. "mime"
  9. "net/http"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "strconv"
  14. "strings"
  15. "compress/gzip"
  16. "encoding/base64"
  17. "archive/zip"
  18. "bitbucket.org/kardianos/osext"
  19. )
  20. const CONFIG_APPEND = "Config.json"
  21. var port *int
  22. var root *string
  23. var workingDir string
  24. var useGzip *bool
  25. var cert *string
  26. var key *string
  27. var allowIP *string
  28. var allowUpload *bool
  29. var uploadDir *string
  30. var username *string
  31. var password *string
  32. var realm *string
  33. var allowZipDir *bool
  34. var useAuth bool
  35. func getConfigFilePath() (string, error) {
  36. //Get Config filename
  37. //remove any extension from executable, then append CONFIG_APPEND const ("Config.json")
  38. // so staticserv.exe OR staticserv -> StaticServConfig.json
  39. exeAbsPath, err := osext.GetExePath()
  40. if err != nil {
  41. return "", err
  42. }
  43. dir, exe := filepath.Split(exeAbsPath)
  44. ext := filepath.Ext(exe)
  45. return filepath.Join(dir, exe[:len(exe)-len(ext)]+CONFIG_APPEND), nil
  46. }
  47. func printHelp() {
  48. flag.PrintDefaults()
  49. }
  50. type config struct {
  51. Port int
  52. AllowOnly string
  53. AllowUpload bool
  54. UploadDir string
  55. TlsCert string
  56. TlsKey string
  57. UseGzip bool
  58. AuthUser string
  59. AuthPass string
  60. AuthRealm string
  61. AllowZip bool
  62. MimeMap map[string]string
  63. }
  64. func defaultConfig() *config {
  65. return &config{
  66. Port: 9000,
  67. AllowOnly: "",
  68. AllowUpload: false,
  69. UploadDir: "",
  70. TlsCert: "",
  71. TlsKey: "",
  72. UseGzip: false,
  73. AuthUser: "",
  74. AuthPass: "",
  75. AuthRealm: "",
  76. AllowZip: true,
  77. MimeMap: map[string]string{
  78. ".png": "image/png",
  79. ".apk": "application/vnd.android.package-archive",
  80. ".cod": "application/vnd.rim.cod",
  81. ".jad": "text/vnd.sun.j2me.app-descriptor",
  82. },
  83. }
  84. }
  85. func main() {
  86. fmt.Printf("Doc Root: %s\nListen On: :%d\n", *root, *port)
  87. server := FileServer(*root, "")
  88. //http.HandleFunc("/", server.runRoot)
  89. http.HandleFunc("/$up", runUp)
  90. http.HandleFunc("/$choose", runChoose)
  91. if *cert != "" && *key != "" {
  92. var err = http.ListenAndServeTLS(":"+strconv.Itoa(*port), *cert, *key, http.HandlerFunc(makeGzipHandler(server)))
  93. if err != nil {
  94. fmt.Printf("Could not start http server:\n%s", err.Error())
  95. }
  96. } else {
  97. var err = http.ListenAndServe(":"+strconv.Itoa(*port), http.HandlerFunc(makeGzipHandler(server)))
  98. if err != nil {
  99. fmt.Printf("Could not start http server:\n%s", err.Error())
  100. }
  101. }
  102. }
  103. type fileHandler struct {
  104. root string
  105. prefix string
  106. }
  107. type gzipResponseWriter struct {
  108. io.Writer
  109. http.ResponseWriter
  110. }
  111. func (w gzipResponseWriter) Write(b []byte) (int, error) {
  112. return w.Writer.Write(b)
  113. }
  114. func makeGzipHandler(handler http.Handler) http.HandlerFunc {
  115. return func(w http.ResponseWriter, r *http.Request) {
  116. if *useGzip == false || !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  117. handler.ServeHTTP(w, r)
  118. return
  119. }
  120. w.Header().Set("Content-Encoding", "gzip")
  121. gz := gzip.NewWriter(w)
  122. defer gz.Close()
  123. handler.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r)
  124. }
  125. }
  126. // FileServer returns a handler that serves HTTP requests
  127. // with the contents of the file system rooted at root.
  128. // It strips prefix from the incoming requests before
  129. // looking up the file name in the file system.
  130. func FileServer(root, prefix string) http.Handler { return &fileHandler{root, prefix} }
  131. func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  132. defer func() {
  133. if err := recover(); err != nil {
  134. fmt.Fprintf(os.Stderr, "Error: %s\n", err)
  135. }
  136. }()
  137. if useAuth {
  138. auth, _ := isAuthorized(r)
  139. if !auth {
  140. sendUnAuth(w)
  141. return
  142. }
  143. }
  144. urlPath := path.Clean(r.URL.Path)
  145. //remove any prefix to the url path like /root/files.txt
  146. if !strings.HasPrefix(urlPath, f.prefix) {
  147. http.NotFound(w, r)
  148. return
  149. }
  150. urlPath = urlPath[len(f.prefix):]
  151. if len(*allowIP) > 0 {
  152. ss := strings.Split(r.RemoteAddr, ":")
  153. if len(ss) < 1 || ss[0] != *allowIP {
  154. http.NotFound(w, r)
  155. fmt.Printf("DENY: %s FOR: %s\n", r.RemoteAddr, urlPath)
  156. return
  157. }
  158. }
  159. //remove request header to always serve fresh
  160. r.Header.Del("If-Modified-Since")
  161. if *allowUpload {
  162. switch urlPath {
  163. case "/$choose":
  164. runChoose(w, r)
  165. return
  166. case "/$up":
  167. runUp(w, r)
  168. return
  169. }
  170. }
  171. serveFile := filepath.Join(f.root, filepath.FromSlash(urlPath))
  172. fileStat, err := os.Stat(serveFile)
  173. if err != nil {
  174. if os.IsNotExist(err) {
  175. // Ignore the common favicon request.
  176. if strings.HasSuffix(serveFile, "favicon.ico") {
  177. return
  178. }
  179. fmt.Printf("Not Found: %s\n", serveFile)
  180. return
  181. }
  182. fmt.Fprintf(os.Stderr, "Error at file <%s>: %s\n", serveFile, err)
  183. return
  184. }
  185. getZip := fileStat.IsDir() && *allowZipDir && r.URL.Query().Get("o") == "zip"
  186. flags := []string{}
  187. if getZip {
  188. flags = append(flags, "zip")
  189. }
  190. fmt.Printf("ALLOW: %s FOR: %s [%s]\n", r.RemoteAddr, urlPath, strings.Join(flags, ","))
  191. if getZip {
  192. zipFileName := "file.zip"
  193. w.Header().Set("Content-Type", "application/zip")
  194. w.Header().Set("Content-Disposition", `attachment; filename="`+zipFileName+`"`)
  195. zw := zip.NewWriter(w)
  196. defer zw.Close()
  197. // Walk directory.
  198. filepath.Walk(serveFile, func(path string, info os.FileInfo, err error) error {
  199. if info.IsDir() {
  200. return nil
  201. }
  202. // Remove base path, convert to forward slash.
  203. zipPath := path[len(serveFile):]
  204. zipPath = strings.TrimLeft(strings.Replace(zipPath, `\`, "/", -1), `/`)
  205. ze, err := zw.Create(zipPath)
  206. if err != nil {
  207. fmt.Fprintf(os.Stderr, "Cannot create zip entry <%s>: %s\n", zipPath, err)
  208. return err
  209. }
  210. file, err := os.Open(path)
  211. if err != nil {
  212. fmt.Fprintf(os.Stderr, "Cannot open file <%s>: %s\n", path, err)
  213. return err
  214. }
  215. defer file.Close()
  216. io.Copy(ze, file)
  217. return nil
  218. })
  219. return
  220. }
  221. http.ServeFile(w, r, serveFile)
  222. }
  223. func isAuthorized(r *http.Request) (hasAuth, tried bool) {
  224. hasAuth = false
  225. tried = false
  226. a := r.Header.Get("Authorization")
  227. if a == "" {
  228. return
  229. }
  230. tried = true
  231. basic := "Basic "
  232. index := strings.Index(a, basic)
  233. if index < 0 {
  234. return
  235. }
  236. upString, err := base64.StdEncoding.DecodeString(a[index+len(basic):])
  237. if err != nil {
  238. return
  239. }
  240. up := strings.SplitN(string(upString), ":", 2)
  241. if len(up) != 2 {
  242. return
  243. }
  244. if *username != up[0] || *password != up[1] {
  245. return
  246. }
  247. hasAuth = true
  248. return
  249. }
  250. func sendUnAuth(w http.ResponseWriter) {
  251. w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", *realm))
  252. w.WriteHeader(http.StatusUnauthorized)
  253. w.Write([]byte("unauthorized"))
  254. }
  255. func runUp(w http.ResponseWriter, req *http.Request) {
  256. //err := req.ParseMultipartForm(60000)
  257. //if err != nil {
  258. // panic(err.String())
  259. //}
  260. formFile, formHead, err := req.FormFile("TheFile")
  261. if err != nil {
  262. return
  263. }
  264. defer formFile.Close()
  265. //remove any directory names in the filename
  266. //START: work around IE sending full filepath and manually get filename
  267. itemHead := formHead.Header["Content-Disposition"][0]
  268. lookfor := "filename=\""
  269. fileIndex := strings.Index(itemHead, lookfor)
  270. if fileIndex < 0 {
  271. panic("runUp: no filename")
  272. }
  273. filename := itemHead[fileIndex+len(lookfor):]
  274. filename = filename[:strings.Index(filename, "\"")]
  275. slashIndex := strings.LastIndex(filename, "\\")
  276. if slashIndex > 0 {
  277. filename = filename[slashIndex+1:]
  278. }
  279. slashIndex = strings.LastIndex(filename, "/")
  280. if slashIndex > 0 {
  281. filename = filename[slashIndex+1:]
  282. }
  283. _, saveToFilename := filepath.Split(filename)
  284. //END: work around IE sending full filepath
  285. //join the filename to the upload dir
  286. saveToFilePath := filepath.Join(*uploadDir, saveToFilename)
  287. osFile, err := os.Create(saveToFilePath)
  288. if err != nil {
  289. panic(err.Error())
  290. }
  291. defer osFile.Close()
  292. count, err := io.Copy(osFile, formFile)
  293. if err != nil {
  294. panic(err.Error())
  295. }
  296. fmt.Printf("ALLOW: %s SAVE: %s (%d)\n", req.RemoteAddr, saveToFilename, count)
  297. w.Write([]byte("Upload Complete for " + filename))
  298. }
  299. func runChoose(w http.ResponseWriter, req *http.Request) {
  300. w.Header().Set("Content-Type", "text/html")
  301. var text = `
  302. <!DOCTYPE html>
  303. <html>
  304. <head>
  305. <script type="text/javascript">
  306. var fileName = '';
  307. function fileSelected() {
  308. try {
  309. var file = document.getElementById('TheFile').files[0];
  310. if (file) {
  311. fileName = file.name;
  312. }
  313. } catch(err) {
  314. //nothing
  315. }
  316. uploadFile();
  317. }
  318. function uploadFile() {
  319. try {
  320. var fd = new FormData();
  321. fd.append("TheFile", document.getElementById('TheFile').files[0]);
  322. var xhr = new XMLHttpRequest();
  323. xhr.upload.addEventListener("progress", uploadProgress, false);
  324. xhr.addEventListener("load", uploadComplete, false);
  325. xhr.addEventListener("error", uploadFailed, false);
  326. xhr.addEventListener("abort", uploadCanceled, false);
  327. xhr.open("POST", "/$up");
  328. xhr.send(fd);
  329. } catch(err) {
  330. document.getElementById("fileForm").submit();
  331. }
  332. }
  333. function uploadProgress(event) {
  334. if (evt.lengthComputable) {
  335. var percentComplete = Math.round(event.loaded * 100 / event.total);
  336. document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
  337. }
  338. }
  339. function uploadComplete(event) {
  340. document.getElementById('progressNumber').innerHTML = 'Upload Complete for ' + fileName;
  341. }
  342. function uploadFailed(event) {
  343. document.getElementById('progressNumber').innerHTML = 'Error';
  344. }
  345. function uploadCanceled(event) {
  346. document.getElementById('progressNumber').innerHTML = 'Upload canceled';
  347. }
  348. </script>
  349. </head>
  350. <body>
  351. <form action="/$up" id="fileForm" enctype="multipart/form-data" method="post">
  352. <input type="file" name="TheFile" id="TheFile" onchange="fileSelected()" style="width: 600px; height: 40px; background: gray;"><BR>
  353. <div id="progressNumber"></div>
  354. </from>
  355. </body>
  356. </html>
  357. `
  358. w.Write([]byte(text))
  359. }
  360. func init() {
  361. defaultConfigFilePath, err := getConfigFilePath()
  362. if err != nil {
  363. panic(err.Error())
  364. }
  365. //load config file here
  366. config := defaultConfig()
  367. configFile, err := os.Open(defaultConfigFilePath)
  368. if err == nil {
  369. decode := json.NewDecoder(configFile)
  370. decode.Decode(config)
  371. }
  372. for ext, mimeType := range config.MimeMap {
  373. err := mime.AddExtensionType(ext, mimeType)
  374. if err != nil {
  375. panic(err.Error())
  376. }
  377. }
  378. if workingDir, err = os.Getwd(); err != nil {
  379. panic(err.Error())
  380. }
  381. port = flag.Int("port", config.Port, "HTTP port number")
  382. root = flag.String("root", workingDir, "Doc Root")
  383. cert = flag.String("cert", config.TlsCert, "TLS cert.pem")
  384. key = flag.String("key", config.TlsKey, "TLS key.pem")
  385. allowIP = flag.String("allow", config.AllowOnly, "Only allow address to connect")
  386. useGzip = flag.Bool("gzip", config.UseGzip, "Use GZip compression when serving files")
  387. allowUpload = flag.Bool("up", config.AllowUpload, "Allow uploads to /$choose, /$up")
  388. uploadDir = flag.String("upTo", config.UploadDir, "Upload files to directory")
  389. username = flag.String("username", config.AuthUser, "Basic Auth Username")
  390. password = flag.String("password", config.AuthPass, "Basic Auth Password")
  391. realm = flag.String("realm", config.AuthRealm, "Basic Auth Realm")
  392. allowZipDir = flag.Bool("zip", config.AllowZip, "Zip children when URL query: ?o=zip")
  393. var printConfig = flag.Bool("printConfig", false, "Prints default Config file")
  394. var help = flag.Bool("h", false, "Prints help")
  395. flag.Parse()
  396. if *printConfig {
  397. //print indented json config file
  398. jsonBytes, err := json.Marshal(defaultConfig())
  399. if err != nil {
  400. panic(err.Error())
  401. }
  402. buffer := new(bytes.Buffer)
  403. json.Indent(buffer, jsonBytes, "", "\t")
  404. fmt.Printf("%s\n", buffer.String())
  405. os.Exit(0)
  406. }
  407. if *help {
  408. printHelp()
  409. os.Exit(0)
  410. }
  411. *root, err = filepath.Abs(*root)
  412. if err != nil {
  413. panic(err.Error())
  414. }
  415. *uploadDir, err = filepath.Abs(*uploadDir)
  416. if err != nil {
  417. panic(err.Error())
  418. }
  419. useAuth = (len(*username) != 0 || len(*password) != 0)
  420. }