Advertisement
Guest User

Untitled

a guest
Oct 16th, 2017
177
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.07 KB | None | 0 0
  1. package main
  2.  
  3. import (
  4. "crypto/md5"
  5. "crypto/tls"
  6. "encoding/json"
  7. "fmt"
  8. "html/template"
  9. "io"
  10. "log"
  11. "mime"
  12. "net/http"
  13. "net/url"
  14. "os"
  15. "os/exec"
  16. "strconv"
  17. "strings"
  18. "time"
  19.  
  20. "github.com/go-chi/chi"
  21. basicauth "gopkg.in/99designs/basicauth-go.v0"
  22. "gopkg.in/mgo.v2"
  23. "gopkg.in/mgo.v2/bson"
  24. )
  25.  
  26. const tpl1 = `
  27. <html>
  28. <head>
  29. <meta name="viewport" content="width=device-width, initial-scale=1">
  30. <title>Upload file</title>
  31. <link rel="stylesheet" href="https://gitcdn.link/repo/Chalarangelo/mini.css/master/dist/mini-default.min.css">
  32. <link rel="stylesheet" href="https://cdn.rawgit.com/Chalarangelo/mini.css/v2.3.4/dist/mini-default.min.css">
  33. <style>
  34. .input-group [type="checkbox"]+label {
  35. margin-left: 1.5rem;
  36. }
  37. </style>
  38. </head>
  39. <body>
  40. <div class="container">
  41. <div class="row">
  42. <div class="col-sm">
  43. <h2>Upload</h2>
  44. </div>
  45. </div>
  46. <div class="row">
  47. <form enctype="multipart/form-data" action="/" method="post" class="col-sm">
  48. <fieldset>
  49. <legend>File Upload</legend>
  50. <div class="input-group vertical">
  51. <input type="file" name="uploadfile" id="uploadfile" />
  52. <label for="uploadfile" class="button">uploadfile</label>
  53. </div>
  54. <div class="input-group vertical">
  55. <label for="pngqlt">pngqlt</label>
  56. <input type="text" name="pngqlt" id="pngqlt" value="60" />
  57. <div class="input-group vertical">
  58. <label for="jpgqlt">jpgqlt</label>
  59. <input type="text" name="jpgqlt" id="jpgqlt" value="75" />
  60. </div>
  61. <div class="input-group vertical">
  62. <input type="checkbox" name="keep" id="keep" value="1" checked />
  63. <label for="keep">keep</label>
  64. </div>
  65. </fieldset>
  66. <input type="submit" class="primary" value="Upload" />
  67. </form>
  68. </div>
  69. </div>
  70. </body>
  71. </html>
  72. `
  73.  
  74. const tpl2 = `
  75. <html>
  76. <head>
  77. <meta name="viewport" content="width=device-width, initial-scale=1">
  78. <title>Upload file</title>
  79. <link rel="stylesheet" href="https://gitcdn.link/repo/Chalarangelo/mini.css/master/dist/mini-default.min.css">
  80. <link rel="stylesheet" href="https://cdn.rawgit.com/Chalarangelo/mini.css/v2.3.4/dist/mini-default.min.css">
  81. <style>
  82. .input-group [type="checkbox"]+label {
  83. margin-left: 1.5rem;
  84. }
  85. </style>
  86. </head>
  87. <body>
  88. <div class="container">
  89. <div class="row">
  90. <div class="col-sm">
  91. <h2>Upload</h2>
  92. </div>
  93. </div>
  94. <div class="row">
  95. <form action="/url" method="post" class="col-sm">
  96. <fieldset>
  97. <legend>File Upload</legend>
  98. <div class="input-group vertical">
  99. <label for="uploadfile">uploadfile</label>
  100. <input type="text" name="uploadfile" id="uploadfile" />
  101. </div>
  102. <div class="input-group vertical">
  103. <label for="pngqlt">pngqlt</label>
  104. <input type="text" name="pngqlt" id="pngqlt" value="60" />
  105. </div>
  106. <div class="input-group vertical">
  107. <label for="jpgqlt">jpgqlt</label>
  108. <input type="text" name="jpgqlt" id="jpgqlt" value="75" />
  109. </div>
  110. <div class="input-group vertical">
  111. <input type="checkbox" name="keep" id="keep" value="1" checked />
  112. <label for="keep">keep</label>
  113. </div>
  114. </fieldset>
  115. <input type="submit" class="primary" value="Upload" />
  116. </form>
  117. </div>
  118. </div>
  119. </body>
  120. </html>
  121. `
  122.  
  123. const tpl3 = `
  124. <html>
  125. <head>
  126. <meta name="viewport" content="width=device-width, initial-scale=1">
  127. <title>Upload file</title>
  128. <link rel="stylesheet" href="https://gitcdn.link/repo/Chalarangelo/mini.css/master/dist/mini-default.min.css">
  129. <link rel="stylesheet" href="https://cdn.rawgit.com/Chalarangelo/mini.css/v2.3.4/dist/mini-default.min.css">
  130. <style>
  131. a {
  132. margin-right: 1rem;
  133. }
  134. a.once {
  135. margin-right: 3.5rem;
  136. }
  137. a.once::after {
  138. background-color: #e53935;
  139. border-radius: 0.25rem;
  140. color: white;
  141. content: "once";
  142. font-size: 0.7rem;
  143. line-height: 1;
  144. margin-left: 0.25rem;
  145. margin-top: -0.25rem;
  146. padding: 0.15rem 0;
  147. position: absolute;
  148. text-align: center;
  149. width: 2.25rem;
  150. }
  151. a:last-child {
  152. margin-right: 0;
  153. }
  154. </style>
  155. </head>
  156. <body>
  157. <div class="container">
  158. <div class="row">
  159. <div class="col-sm">
  160. <h2>Store</h2>
  161. </div>
  162. </div>
  163. <div class="row">
  164. <div class="col-sm">
  165. <p>
  166. {{range .}}
  167. <a
  168. href="/store/{{.ID.Hex}}"
  169. {{if eq .Keep 0}}
  170. class="once"
  171. {{end}}
  172. >{{.Name}}</a>
  173. {{end}}
  174. </p>
  175. </div>
  176. </div>
  177. </div>
  178. <script type="text/javascript">
  179. document
  180. .querySelectorAll(".once")
  181. .forEach(function (elm) {
  182. elm
  183. .addEventListener(
  184. "click",
  185. function (evt) {
  186. evt.target.remove();
  187. }
  188. );
  189. });
  190. </script>
  191. </body>
  192. </html>
  193. `
  194.  
  195. // Entry is a file entry
  196. type Entry struct {
  197. ID bson.ObjectId `bson:"_id,omitempty"`
  198. Name string `bson:"name"`
  199. Path string `bson:"path"`
  200. ContentType string `bson:"content_type"`
  201. InSize int64 `bson:"in_size"`
  202. OutSize int64 `bson:"out_size"`
  203. User string `bson:"user"`
  204. Keep int8 `bson:"keep"`
  205. Timestamp time.Time `bson:"timestamp"`
  206. }
  207.  
  208. type User struct {
  209. ID bson.ObjectId `bson:"_id,omitempty"`
  210. name string `bson:"name"`
  211. }
  212.  
  213. func createAuthMiddleware(realm string, s *mgo.Session) func(http.Handler) http.Handler {
  214. return func(next http.Handler) http.Handler {
  215. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  216. name, password, ok := r.BasicAuth()
  217.  
  218. if !ok {
  219. unauthorized(w, realm)
  220. return
  221. }
  222.  
  223. session := s.Copy()
  224. defer session.Close()
  225.  
  226. u := User{}
  227. session.DB("store").C("users").Find(bson.M{"name": name}).One(&u)
  228.  
  229. if !userFound {
  230. unauthorized(w, realm)
  231. return
  232. }
  233.  
  234. for _, validPassword := range validPasswords {
  235. if password == validPassword {
  236. next.ServeHTTP(w, r)
  237. return
  238. }
  239. }
  240.  
  241. unauthorized(w, realm)
  242. })
  243. }
  244. }
  245.  
  246. func unauthorized(w http.ResponseWriter, realm string) {
  247. w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
  248. w.WriteHeader(http.StatusUnauthorized)
  249. }
  250.  
  251. func calcPath(fn string) string {
  252. crutime := time.Now().Unix()
  253. h := md5.New()
  254. io.WriteString(h, fmt.Sprintf("%s + %s", strconv.FormatInt(crutime, 20), fn))
  255. hash := h.Sum(nil)
  256.  
  257. return fmt.Sprintf("./store/%x-%s", hash, fn)
  258. }
  259.  
  260. func optImg(pt string, ct string, pngqlt int, jpgqlt int) error {
  261. if ct == "image/png" {
  262. cmd := exec.Command("pngquant", "--quality", fmt.Sprintf("%s-%s", strconv.Itoa(pngqlt), strconv.Itoa(pngqlt)), "--ext", ".png", "--force", pt)
  263. if err := cmd.Run(); err != nil {
  264. return err
  265. }
  266. }
  267.  
  268. if ct == "image/jpeg" {
  269. cmd := exec.Command("jpegoptim", "-m", strconv.Itoa(jpgqlt), pt)
  270. if err := cmd.Run(); err != nil {
  271. return err
  272. }
  273. }
  274. return nil
  275. }
  276.  
  277. func uploadGet(w http.ResponseWriter, r *http.Request) {
  278. t, _ := template.New("upload").Parse(tpl1)
  279. t.Execute(w, nil)
  280. }
  281.  
  282. func uploadPost(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
  283. return func(w http.ResponseWriter, r *http.Request) {
  284. r.ParseMultipartForm(32 << 20)
  285.  
  286. jpgqlt, err := strconv.Atoi(r.Form.Get("jpgqlt"))
  287. if err != nil || jpgqlt < 0 || jpgqlt > 100 {
  288. jpgqlt = 75
  289. }
  290.  
  291. pngqlt, err := strconv.Atoi(r.Form.Get("pngqlt"))
  292. if err != nil || pngqlt < 0 || pngqlt > 100 {
  293. pngqlt = 60
  294. }
  295.  
  296. keep, err := strconv.ParseInt(r.Form.Get("keep"), 10, 8)
  297. if err != nil {
  298. keep = 0
  299. }
  300.  
  301. f, fh, err := r.FormFile("uploadfile")
  302. if err != nil {
  303. errStr := fmt.Sprintf("FormFile: %s", err.Error())
  304. log.Println(errStr)
  305. http.Error(w, errStr, 400)
  306. return
  307. }
  308. defer f.Close()
  309.  
  310. pt := calcPath(fh.Filename)
  311.  
  312. fo, err := os.OpenFile(pt, os.O_RDWR|os.O_CREATE, 0666)
  313. if err != nil {
  314. errStr := fmt.Sprintf("OpenFile: %s", err.Error())
  315. log.Println(errStr)
  316. http.Error(w, errStr, 500)
  317. return
  318. }
  319. defer fo.Close()
  320.  
  321. io.Copy(fo, f)
  322.  
  323. err = optImg(fo.Name(), fh.Header.Get("Content-Type"), pngqlt, jpgqlt)
  324. if err != nil {
  325. errStr := fmt.Sprintf("OptImg %s: %s", fh.Header.Get("Content-Type"), err.Error())
  326. log.Println(errStr)
  327. http.Error(w, errStr, 400)
  328.  
  329. os.Remove(fo.Name())
  330. return
  331. }
  332.  
  333. fi, _ := os.Stat(fo.Name())
  334. un, _, _ := r.BasicAuth()
  335.  
  336. sc := s.Copy()
  337. defer sc.Close()
  338.  
  339. id := bson.NewObjectId()
  340. e := &Entry{
  341. ID: id,
  342. Name: fh.Filename,
  343. Path: fo.Name(),
  344. ContentType: fh.Header.Get("Content-Type"),
  345. InSize: r.ContentLength,
  346. OutSize: fi.Size(),
  347. User: un,
  348. Keep: int8(keep),
  349. Timestamp: time.Now(),
  350. }
  351. err = sc.DB("store").C("entries").Insert(e)
  352. if err != nil {
  353. errStr := fmt.Sprintf("DbInsert: %s", err.Error())
  354. log.Println(errStr)
  355. http.Error(w, errStr, 500)
  356. }
  357.  
  358. respBody, err := json.MarshalIndent(e, "", " ")
  359. if err != nil {
  360. errStr := fmt.Sprintf("MarshalIndent: %s", err.Error())
  361. log.Println(errStr)
  362. http.Error(w, errStr, 500)
  363. }
  364.  
  365. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  366. w.Write(respBody)
  367. }
  368. }
  369.  
  370. func urlGet(w http.ResponseWriter, r *http.Request) {
  371. t, _ := template.New("upload").Parse(tpl2)
  372. t.Execute(w, nil)
  373. }
  374.  
  375. func urlPost(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
  376. return func(w http.ResponseWriter, r *http.Request) {
  377. r.ParseForm()
  378.  
  379. jpgqlt, err := strconv.Atoi(r.Form.Get("jpgqlt"))
  380. if err != nil || jpgqlt < 0 || jpgqlt > 100 {
  381. jpgqlt = 75
  382. }
  383.  
  384. pngqlt, err := strconv.Atoi(r.Form.Get("pngqlt"))
  385. if err != nil || pngqlt < 0 || pngqlt > 100 {
  386. pngqlt = 60
  387. }
  388.  
  389. keep, err := strconv.ParseInt(r.Form.Get("keep"), 10, 8)
  390. if err != nil {
  391. keep = 0
  392. }
  393.  
  394. uri := r.Form.Get("uploadfile")
  395. if _, err = url.ParseRequestURI(uri); err != nil {
  396. errStr := fmt.Sprintf("ParseRequestURI: %s", err.Error())
  397. log.Println(errStr)
  398. http.Error(w, errStr, 400)
  399. return
  400. }
  401.  
  402. tr := &http.Transport{
  403. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  404. }
  405. client := &http.Client{Transport: tr}
  406.  
  407. resp, err := client.Get(uri)
  408. if err != nil {
  409. errStr := fmt.Sprintf("ClientGet: %s", err.Error())
  410. log.Println(errStr)
  411. http.Error(w, errStr, 400)
  412. return
  413. }
  414. defer resp.Body.Close()
  415.  
  416. ct := resp.Header.Get("Content-Type")
  417.  
  418. sl := strings.Split(resp.Request.URL.Path, "/")
  419. sl = strings.Split(sl[len(sl)-1], ".")
  420.  
  421. fn := strings.Join(sl[0:len(sl)-1], ".")
  422.  
  423. if len(fn) == 0 {
  424. fn = "untitled"
  425. }
  426.  
  427. var ex string
  428. if len(sl) > 1 {
  429. ex = fmt.Sprintf(".%s", sl[len(sl)-1])
  430. }
  431.  
  432. if len(ex) == 0 {
  433. exs, _ := mime.ExtensionsByType(ct)
  434. if len(exs) > 0 {
  435. ex = exs[0]
  436. }
  437. }
  438.  
  439. fn += ex
  440. pt := calcPath(fn)
  441.  
  442. fo, err := os.OpenFile(pt, os.O_RDWR|os.O_CREATE, 0666)
  443. if err != nil {
  444. errStr := fmt.Sprintf("OpenFile: %s", err.Error())
  445. log.Println(errStr)
  446. http.Error(w, errStr, 500)
  447. return
  448. }
  449. defer fo.Close()
  450.  
  451. io.Copy(fo, resp.Body)
  452. err = optImg(fo.Name(), ct, pngqlt, jpgqlt)
  453. if err != nil {
  454. errStr := fmt.Sprintf("OptImg %s: %s", ct, err.Error())
  455. log.Println(errStr)
  456. http.Error(w, errStr, 400)
  457.  
  458. os.Remove(fo.Name())
  459. return
  460. }
  461.  
  462. fi, _ := os.Stat(fo.Name())
  463. un, _, _ := r.BasicAuth()
  464.  
  465. sc := s.Copy()
  466. defer sc.Close()
  467.  
  468. id := bson.NewObjectId()
  469. e := &Entry{
  470. ID: id,
  471. Name: fn,
  472. Path: fo.Name(),
  473. ContentType: ct,
  474. InSize: resp.ContentLength,
  475. OutSize: fi.Size(),
  476. User: un,
  477. Keep: int8(keep),
  478. Timestamp: time.Now(),
  479. }
  480. err = sc.DB("store").C("entries").Insert(e)
  481. if err != nil {
  482. errStr := fmt.Sprintf("DbInsert: %s", err.Error())
  483. log.Println(errStr)
  484. http.Error(w, errStr, 500)
  485. return
  486. }
  487.  
  488. respBody, err := json.MarshalIndent(e, "", " ")
  489. if err != nil {
  490. errStr := fmt.Sprintf("MarshalIndent: %s", err.Error())
  491. log.Println(errStr)
  492. http.Error(w, errStr, 500)
  493. return
  494. }
  495.  
  496. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  497. w.Write(respBody)
  498. }
  499. }
  500.  
  501. func serveFiles(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
  502. return func(w http.ResponseWriter, r *http.Request) {
  503. session := s.Copy()
  504. defer session.Close()
  505.  
  506. id := chi.URLParam(r, "id")
  507. if ch := bson.IsObjectIdHex(id); !ch {
  508. errStr := fmt.Sprintf("IsObjectIdHex: %t", ch)
  509. log.Println(errStr)
  510. http.Error(w, errStr, 400)
  511. return
  512. }
  513.  
  514. e := Entry{}
  515. err := session.DB("store").C("entries").Find(bson.M{"_id": bson.ObjectIdHex(id)}).One(&e)
  516. if err != nil {
  517. errStr := fmt.Sprintf("DbFindOne: %s", err.Error())
  518. log.Println(errStr)
  519. http.Error(w, errStr, 404)
  520. return
  521. }
  522.  
  523. f, err := os.OpenFile(e.Path, os.O_RDONLY, 0666)
  524. if err != nil {
  525. errStr := fmt.Sprintf("OpenFile: %s", err.Error())
  526. log.Println(errStr)
  527. http.Error(w, errStr, 404)
  528.  
  529. err := session.DB("store").C("entries").RemoveId(e.ID)
  530. if err != nil {
  531. errStr := fmt.Sprintf("DbRemoveId: %s", err.Error())
  532. log.Println(errStr)
  533. http.Error(w, errStr, 500)
  534. return
  535. }
  536. return
  537. }
  538. defer f.Close()
  539.  
  540. w.Header().Set("Content-Disposition", "attachment; filename="+e.Name)
  541. w.Header().Set("Content-Type", e.ContentType)
  542.  
  543. io.Copy(w, f)
  544.  
  545. if e.Keep == 0 {
  546. os.Remove(e.Path)
  547. err := session.DB("store").C("entries").RemoveId(e.ID)
  548. if err != nil {
  549. errStr := fmt.Sprintf("DbRemoveId: %s", err.Error())
  550. log.Println(errStr)
  551. http.Error(w, errStr, 500)
  552. return
  553. }
  554. }
  555. }
  556. }
  557.  
  558. func showFiles(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
  559. return func(w http.ResponseWriter, r *http.Request) {
  560. session := s.Copy()
  561. defer session.Close()
  562.  
  563. un, _, _ := r.BasicAuth()
  564.  
  565. var es []Entry
  566. err := session.DB("store").C("entries").Find(bson.M{"user": un}).Sort("-timestamp").Limit(500).All(&es)
  567. if err != nil {
  568. errStr := fmt.Sprintf("DbFindAll: %s", err.Error())
  569. log.Println(errStr)
  570. http.Error(w, errStr, 500)
  571. return
  572. }
  573.  
  574. t, _ := template.New("upload").Parse(tpl3)
  575. err = t.Execute(w, es)
  576. if err != nil {
  577. errStr := fmt.Sprintf("TemplateExecute: %s", err.Error())
  578. log.Println(errStr)
  579. http.Error(w, errStr, 500)
  580. return
  581. }
  582. }
  583. }
  584.  
  585. func cleanGet(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
  586. return func(w http.ResponseWriter, r *http.Request) {
  587. session := s.Copy()
  588. defer session.Close()
  589.  
  590. var dtk int
  591. dtk, _ = strconv.Atoi(os.Getenv("DAYS_TO_KEEP"))
  592.  
  593. var es []Entry
  594. err := session.DB("store").C("entries").Find(bson.M{"timestamp": bson.M{"$lt": time.Now().AddDate(0, 0, -dtk)}}).All(&es)
  595. if err != nil {
  596. errStr := fmt.Sprintf("DbFindAll: %s", err.Error())
  597. log.Println(errStr)
  598. http.Error(w, errStr, 400)
  599. return
  600. }
  601.  
  602. for _, e := range es {
  603. os.Remove(e.Path)
  604. err := session.DB("store").C("entries").RemoveId(e.ID)
  605. if err != nil {
  606. errStr := fmt.Sprintf("DbRemoveId: %s", err.Error())
  607. log.Println(errStr)
  608. http.Error(w, errStr, 400)
  609. return
  610. }
  611. }
  612.  
  613. resStr := fmt.Sprintf("Cleaned: %d", len(es))
  614.  
  615. io.WriteString(w, resStr)
  616. log.Println(resStr)
  617. }
  618. }
  619.  
  620. func main() {
  621. mgoAddr := strings.Join([]string{os.Getenv("DB_PORT_27017_TCP_ADDR"), os.Getenv("DB_PORT_27017_TCP_PORT")}, ":")
  622. session, err := mgo.Dial(mgoAddr)
  623. if err != nil {
  624. log.Fatal(err)
  625. }
  626. defer session.Close()
  627. session.SetMode(mgo.Monotonic, true)
  628.  
  629. r := chi.NewRouter()
  630.  
  631. authMiddleware := basicauth.NewFromEnv("MyRealm", "AUTH_")
  632.  
  633. r.Route("/", func(r chi.Router) {
  634. r.Use(authMiddleware)
  635. r.Get("/", uploadGet)
  636. r.Post("/", uploadPost(session))
  637. })
  638.  
  639. r.Route("/url", func(r chi.Router) {
  640. r.Use(authMiddleware)
  641. r.Get("/", urlGet)
  642. r.Post("/", urlPost(session))
  643. })
  644.  
  645. r.Route("/store", func(r chi.Router) {
  646. r.With(authMiddleware).Get("/", showFiles(session))
  647. r.Get("/{id}", serveFiles(session))
  648. })
  649.  
  650. r.Route("/clean", func(r chi.Router) {
  651. r.Get("/", cleanGet(session))
  652. })
  653.  
  654. servAddr := strings.Join([]string{os.Getenv("HOST"), os.Getenv("PORT")}, ":")
  655. err = http.ListenAndServe(servAddr, r)
  656. if err != nil {
  657. log.Fatal("ListenAndServe: ", err)
  658. }
  659. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement