utils.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package main
  2. import (
  3. "bytes"
  4. "crypto/rand"
  5. "fmt"
  6. "log"
  7. "mime/multipart"
  8. "net/http"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. "github.com/disintegration/imaging"
  13. "github.com/labstack/echo"
  14. "github.com/lib/pq"
  15. )
  16. var (
  17. // This replaces all special characters
  18. tagRegexp = regexp.MustCompile(`[^a-z0-9\-\s]`)
  19. tagRegexpSpaces = regexp.MustCompile(`[\s]+`)
  20. )
  21. // validateMIME is a helper function to validate uploaded file's MIME type
  22. // against the slice of MIME types is given.
  23. func validateMIME(typ string, mimes []string) (ok bool) {
  24. if len(mimes) > 0 {
  25. var (
  26. ok = false
  27. )
  28. for _, m := range mimes {
  29. if typ == m {
  30. ok = true
  31. break
  32. }
  33. }
  34. if !ok {
  35. return false
  36. }
  37. }
  38. return true
  39. }
  40. // generateFileName appends the incoming file's name with a small random hash.
  41. func generateFileName(fName string) string {
  42. name := strings.TrimSpace(fName)
  43. if name == "" {
  44. name, _ = generateRandomString(10)
  45. }
  46. return name
  47. }
  48. // createThumbnail reads the file object and returns a smaller image
  49. func createThumbnail(file *multipart.FileHeader) (*bytes.Reader, error) {
  50. src, err := file.Open()
  51. if err != nil {
  52. return nil, err
  53. }
  54. defer src.Close()
  55. img, err := imaging.Decode(src)
  56. if err != nil {
  57. return nil, echo.NewHTTPError(http.StatusInternalServerError,
  58. fmt.Sprintf("Error decoding image: %v", err))
  59. }
  60. t := imaging.Resize(img, thumbnailSize, 0, imaging.Lanczos)
  61. // Encode the image into a byte slice as PNG.
  62. var buf bytes.Buffer
  63. err = imaging.Encode(&buf, t, imaging.PNG)
  64. if err != nil {
  65. log.Fatal(err)
  66. }
  67. return bytes.NewReader(buf.Bytes()), nil
  68. }
  69. // Given an error, pqErrMsg will try to return pq error details
  70. // if it's a pq error.
  71. func pqErrMsg(err error) string {
  72. if err, ok := err.(*pq.Error); ok {
  73. if err.Detail != "" {
  74. return fmt.Sprintf("%s. %s", err, err.Detail)
  75. }
  76. }
  77. return err.Error()
  78. }
  79. // normalizeTags takes a list of string tags and normalizes them by
  80. // lowercasing and removing all special characters except for dashes.
  81. func normalizeTags(tags []string) []string {
  82. var (
  83. out []string
  84. space = []byte(" ")
  85. dash = []byte("-")
  86. )
  87. for _, t := range tags {
  88. rep := bytes.TrimSpace(tagRegexp.ReplaceAll(bytes.ToLower([]byte(t)), space))
  89. rep = tagRegexpSpaces.ReplaceAll(rep, dash)
  90. if len(rep) > 0 {
  91. out = append(out, string(rep))
  92. }
  93. }
  94. return out
  95. }
  96. // makeMsgTpl takes a page title, heading, and message and returns
  97. // a msgTpl that can be rendered as a HTML view. This is used for
  98. // rendering arbitrary HTML views with error and success messages.
  99. func makeMsgTpl(pageTitle, heading, msg string) msgTpl {
  100. if heading == "" {
  101. heading = pageTitle
  102. }
  103. err := msgTpl{}
  104. err.Title = pageTitle
  105. err.MessageTitle = heading
  106. err.Message = msg
  107. return err
  108. }
  109. // parseStringIDs takes a slice of numeric string IDs and
  110. // parses each number into an int64 and returns a slice of the
  111. // resultant values.
  112. func parseStringIDs(s []string) ([]int64, error) {
  113. vals := make([]int64, 0, len(s))
  114. for _, v := range s {
  115. i, err := strconv.ParseInt(v, 10, 64)
  116. if err != nil {
  117. return nil, err
  118. }
  119. if i < 1 {
  120. return nil, fmt.Errorf("%d is not a valid ID", i)
  121. }
  122. vals = append(vals, i)
  123. }
  124. return vals, nil
  125. }
  126. // generateRandomString generates a cryptographically random, alphanumeric string of length n.
  127. func generateRandomString(n int) (string, error) {
  128. const dictionary = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  129. var bytes = make([]byte, n)
  130. if _, err := rand.Read(bytes); err != nil {
  131. return "", err
  132. }
  133. for k, v := range bytes {
  134. bytes[k] = dictionary[v%byte(len(dictionary))]
  135. }
  136. return string(bytes), nil
  137. }