You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tool.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package base
  5. import (
  6. "crypto/md5"
  7. "crypto/rand"
  8. "crypto/sha1"
  9. "encoding/base64"
  10. "encoding/hex"
  11. "fmt"
  12. "html/template"
  13. "io"
  14. "math"
  15. "net/http"
  16. "net/url"
  17. "path"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "unicode"
  22. "unicode/utf8"
  23. "code.gitea.io/gitea/modules/log"
  24. "code.gitea.io/gitea/modules/setting"
  25. "code.gitea.io/gitea/modules/util"
  26. "github.com/Unknwon/com"
  27. "github.com/Unknwon/i18n"
  28. "github.com/gogits/chardet"
  29. )
  30. // EncodeMD5 encodes string to md5 hex value.
  31. func EncodeMD5(str string) string {
  32. m := md5.New()
  33. m.Write([]byte(str))
  34. return hex.EncodeToString(m.Sum(nil))
  35. }
  36. // EncodeSha1 string to sha1 hex value.
  37. func EncodeSha1(str string) string {
  38. h := sha1.New()
  39. h.Write([]byte(str))
  40. return hex.EncodeToString(h.Sum(nil))
  41. }
  42. // ShortSha is basically just truncating.
  43. // It is DEPRECATED and will be removed in the future.
  44. func ShortSha(sha1 string) string {
  45. return TruncateString(sha1, 10)
  46. }
  47. // DetectEncoding detect the encoding of content
  48. func DetectEncoding(content []byte) (string, error) {
  49. if utf8.Valid(content) {
  50. log.Debug("Detected encoding: utf-8 (fast)")
  51. return "UTF-8", nil
  52. }
  53. result, err := chardet.NewTextDetector().DetectBest(content)
  54. if err != nil {
  55. return "", err
  56. }
  57. if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
  58. log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
  59. return setting.Repository.AnsiCharset, err
  60. }
  61. log.Debug("Detected encoding: %s", result.Charset)
  62. return result.Charset, err
  63. }
  64. // BasicAuthDecode decode basic auth string
  65. func BasicAuthDecode(encoded string) (string, string, error) {
  66. s, err := base64.StdEncoding.DecodeString(encoded)
  67. if err != nil {
  68. return "", "", err
  69. }
  70. auth := strings.SplitN(string(s), ":", 2)
  71. return auth[0], auth[1], nil
  72. }
  73. // BasicAuthEncode encode basic auth string
  74. func BasicAuthEncode(username, password string) string {
  75. return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
  76. }
  77. // GetRandomBytesAsBase64 generates a random base64 string from n bytes
  78. func GetRandomBytesAsBase64(n int) string {
  79. bytes := make([]byte, 32)
  80. _, err := io.ReadFull(rand.Reader, bytes)
  81. if err != nil {
  82. log.Fatal(4, "Error reading random bytes: %v", err)
  83. }
  84. return base64.RawURLEncoding.EncodeToString(bytes)
  85. }
  86. // VerifyTimeLimitCode verify time limit code
  87. func VerifyTimeLimitCode(data string, minutes int, code string) bool {
  88. if len(code) <= 18 {
  89. return false
  90. }
  91. // split code
  92. start := code[:12]
  93. lives := code[12:18]
  94. if d, err := com.StrTo(lives).Int(); err == nil {
  95. minutes = d
  96. }
  97. // right active code
  98. retCode := CreateTimeLimitCode(data, minutes, start)
  99. if retCode == code && minutes > 0 {
  100. // check time is expired or not
  101. before, _ := time.ParseInLocation("200601021504", start, time.Local)
  102. now := time.Now()
  103. if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
  104. return true
  105. }
  106. }
  107. return false
  108. }
  109. // TimeLimitCodeLength default value for time limit code
  110. const TimeLimitCodeLength = 12 + 6 + 40
  111. // CreateTimeLimitCode create a time limit code
  112. // code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
  113. func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string {
  114. format := "200601021504"
  115. var start, end time.Time
  116. var startStr, endStr string
  117. if startInf == nil {
  118. // Use now time create code
  119. start = time.Now()
  120. startStr = start.Format(format)
  121. } else {
  122. // use start string create code
  123. startStr = startInf.(string)
  124. start, _ = time.ParseInLocation(format, startStr, time.Local)
  125. startStr = start.Format(format)
  126. }
  127. end = start.Add(time.Minute * time.Duration(minutes))
  128. endStr = end.Format(format)
  129. // create sha1 encode string
  130. sh := sha1.New()
  131. sh.Write([]byte(data + setting.SecretKey + startStr + endStr + com.ToStr(minutes)))
  132. encoded := hex.EncodeToString(sh.Sum(nil))
  133. code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
  134. return code
  135. }
  136. // HashEmail hashes email address to MD5 string.
  137. // https://en.gravatar.com/site/implement/hash/
  138. func HashEmail(email string) string {
  139. return EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
  140. }
  141. // DefaultAvatarLink the default avatar link
  142. func DefaultAvatarLink() string {
  143. return setting.AppSubURL + "/img/avatar_default.png"
  144. }
  145. // DefaultAvatarSize is a sentinel value for the default avatar size, as
  146. // determined by the avatar-hosting service.
  147. const DefaultAvatarSize = -1
  148. // libravatarURL returns the URL for the given email. This function should only
  149. // be called if a federated avatar service is enabled.
  150. func libravatarURL(email string) (*url.URL, error) {
  151. urlStr, err := setting.LibravatarService.FromEmail(email)
  152. if err != nil {
  153. log.Error(4, "LibravatarService.FromEmail(email=%s): error %v", email, err)
  154. return nil, err
  155. }
  156. u, err := url.Parse(urlStr)
  157. if err != nil {
  158. log.Error(4, "Failed to parse libravatar url(%s): error %v", urlStr, err)
  159. return nil, err
  160. }
  161. return u, nil
  162. }
  163. // SizedAvatarLink returns a sized link to the avatar for the given email
  164. // address.
  165. func SizedAvatarLink(email string, size int) string {
  166. var avatarURL *url.URL
  167. if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
  168. var err error
  169. avatarURL, err = libravatarURL(email)
  170. if err != nil {
  171. return DefaultAvatarLink()
  172. }
  173. } else if !setting.DisableGravatar {
  174. // copy GravatarSourceURL, because we will modify its Path.
  175. copyOfGravatarSourceURL := *setting.GravatarSourceURL
  176. avatarURL = &copyOfGravatarSourceURL
  177. avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
  178. } else {
  179. return DefaultAvatarLink()
  180. }
  181. vals := avatarURL.Query()
  182. vals.Set("d", "identicon")
  183. if size != DefaultAvatarSize {
  184. vals.Set("s", strconv.Itoa(size))
  185. }
  186. avatarURL.RawQuery = vals.Encode()
  187. return avatarURL.String()
  188. }
  189. // AvatarLink returns relative avatar link to the site domain by given email,
  190. // which includes app sub-url as prefix. However, it is possible
  191. // to return full URL if user enables Gravatar-like service.
  192. func AvatarLink(email string) string {
  193. return SizedAvatarLink(email, DefaultAvatarSize)
  194. }
  195. // Seconds-based time units
  196. const (
  197. Minute = 60
  198. Hour = 60 * Minute
  199. Day = 24 * Hour
  200. Week = 7 * Day
  201. Month = 30 * Day
  202. Year = 12 * Month
  203. )
  204. func computeTimeDiff(diff int64, lang string) (int64, string) {
  205. diffStr := ""
  206. switch {
  207. case diff <= 0:
  208. diff = 0
  209. diffStr = i18n.Tr(lang, "tool.now")
  210. case diff < 2:
  211. diff = 0
  212. diffStr = i18n.Tr(lang, "tool.1s")
  213. case diff < 1*Minute:
  214. diffStr = i18n.Tr(lang, "tool.seconds", diff)
  215. diff = 0
  216. case diff < 2*Minute:
  217. diff -= 1 * Minute
  218. diffStr = i18n.Tr(lang, "tool.1m")
  219. case diff < 1*Hour:
  220. diffStr = i18n.Tr(lang, "tool.minutes", diff/Minute)
  221. diff -= diff / Minute * Minute
  222. case diff < 2*Hour:
  223. diff -= 1 * Hour
  224. diffStr = i18n.Tr(lang, "tool.1h")
  225. case diff < 1*Day:
  226. diffStr = i18n.Tr(lang, "tool.hours", diff/Hour)
  227. diff -= diff / Hour * Hour
  228. case diff < 2*Day:
  229. diff -= 1 * Day
  230. diffStr = i18n.Tr(lang, "tool.1d")
  231. case diff < 1*Week:
  232. diffStr = i18n.Tr(lang, "tool.days", diff/Day)
  233. diff -= diff / Day * Day
  234. case diff < 2*Week:
  235. diff -= 1 * Week
  236. diffStr = i18n.Tr(lang, "tool.1w")
  237. case diff < 1*Month:
  238. diffStr = i18n.Tr(lang, "tool.weeks", diff/Week)
  239. diff -= diff / Week * Week
  240. case diff < 2*Month:
  241. diff -= 1 * Month
  242. diffStr = i18n.Tr(lang, "tool.1mon")
  243. case diff < 1*Year:
  244. diffStr = i18n.Tr(lang, "tool.months", diff/Month)
  245. diff -= diff / Month * Month
  246. case diff < 2*Year:
  247. diff -= 1 * Year
  248. diffStr = i18n.Tr(lang, "tool.1y")
  249. default:
  250. diffStr = i18n.Tr(lang, "tool.years", diff/Year)
  251. diff -= (diff / Year) * Year
  252. }
  253. return diff, diffStr
  254. }
  255. // MinutesToFriendly returns a user friendly string with number of minutes
  256. // converted to hours and minutes.
  257. func MinutesToFriendly(minutes int, lang string) string {
  258. duration := time.Duration(minutes) * time.Minute
  259. return TimeSincePro(time.Now().Add(-duration), lang)
  260. }
  261. // TimeSincePro calculates the time interval and generate full user-friendly string.
  262. func TimeSincePro(then time.Time, lang string) string {
  263. return timeSincePro(then, time.Now(), lang)
  264. }
  265. func timeSincePro(then, now time.Time, lang string) string {
  266. diff := now.Unix() - then.Unix()
  267. if then.After(now) {
  268. return i18n.Tr(lang, "tool.future")
  269. }
  270. if diff == 0 {
  271. return i18n.Tr(lang, "tool.now")
  272. }
  273. var timeStr, diffStr string
  274. for {
  275. if diff == 0 {
  276. break
  277. }
  278. diff, diffStr = computeTimeDiff(diff, lang)
  279. timeStr += ", " + diffStr
  280. }
  281. return strings.TrimPrefix(timeStr, ", ")
  282. }
  283. func timeSince(then, now time.Time, lang string) string {
  284. return timeSinceUnix(then.Unix(), now.Unix(), lang)
  285. }
  286. func timeSinceUnix(then, now int64, lang string) string {
  287. lbl := "tool.ago"
  288. diff := now - then
  289. if then > now {
  290. lbl = "tool.from_now"
  291. diff = then - now
  292. }
  293. if diff <= 0 {
  294. return i18n.Tr(lang, "tool.now")
  295. }
  296. _, diffStr := computeTimeDiff(diff, lang)
  297. return i18n.Tr(lang, lbl, diffStr)
  298. }
  299. // RawTimeSince retrieves i18n key of time since t
  300. func RawTimeSince(t time.Time, lang string) string {
  301. return timeSince(t, time.Now(), lang)
  302. }
  303. // TimeSince calculates the time interval and generate user-friendly string.
  304. func TimeSince(then time.Time, lang string) template.HTML {
  305. return htmlTimeSince(then, time.Now(), lang)
  306. }
  307. func htmlTimeSince(then, now time.Time, lang string) template.HTML {
  308. return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
  309. then.Format(setting.TimeFormat),
  310. timeSince(then, now, lang)))
  311. }
  312. // TimeSinceUnix calculates the time interval and generate user-friendly string.
  313. func TimeSinceUnix(then util.TimeStamp, lang string) template.HTML {
  314. return htmlTimeSinceUnix(then, util.TimeStamp(time.Now().Unix()), lang)
  315. }
  316. func htmlTimeSinceUnix(then, now util.TimeStamp, lang string) template.HTML {
  317. return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
  318. then.Format(setting.TimeFormat),
  319. timeSinceUnix(int64(then), int64(now), lang)))
  320. }
  321. // Storage space size types
  322. const (
  323. Byte = 1
  324. KByte = Byte * 1024
  325. MByte = KByte * 1024
  326. GByte = MByte * 1024
  327. TByte = GByte * 1024
  328. PByte = TByte * 1024
  329. EByte = PByte * 1024
  330. )
  331. var bytesSizeTable = map[string]uint64{
  332. "b": Byte,
  333. "kb": KByte,
  334. "mb": MByte,
  335. "gb": GByte,
  336. "tb": TByte,
  337. "pb": PByte,
  338. "eb": EByte,
  339. }
  340. func logn(n, b float64) float64 {
  341. return math.Log(n) / math.Log(b)
  342. }
  343. func humanateBytes(s uint64, base float64, sizes []string) string {
  344. if s < 10 {
  345. return fmt.Sprintf("%dB", s)
  346. }
  347. e := math.Floor(logn(float64(s), base))
  348. suffix := sizes[int(e)]
  349. val := float64(s) / math.Pow(base, math.Floor(e))
  350. f := "%.0f"
  351. if val < 10 {
  352. f = "%.1f"
  353. }
  354. return fmt.Sprintf(f+"%s", val, suffix)
  355. }
  356. // FileSize calculates the file size and generate user-friendly string.
  357. func FileSize(s int64) string {
  358. sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
  359. return humanateBytes(uint64(s), 1024, sizes)
  360. }
  361. // Subtract deals with subtraction of all types of number.
  362. func Subtract(left interface{}, right interface{}) interface{} {
  363. var rleft, rright int64
  364. var fleft, fright float64
  365. var isInt = true
  366. switch left.(type) {
  367. case int:
  368. rleft = int64(left.(int))
  369. case int8:
  370. rleft = int64(left.(int8))
  371. case int16:
  372. rleft = int64(left.(int16))
  373. case int32:
  374. rleft = int64(left.(int32))
  375. case int64:
  376. rleft = left.(int64)
  377. case float32:
  378. fleft = float64(left.(float32))
  379. isInt = false
  380. case float64:
  381. fleft = left.(float64)
  382. isInt = false
  383. }
  384. switch right.(type) {
  385. case int:
  386. rright = int64(right.(int))
  387. case int8:
  388. rright = int64(right.(int8))
  389. case int16:
  390. rright = int64(right.(int16))
  391. case int32:
  392. rright = int64(right.(int32))
  393. case int64:
  394. rright = right.(int64)
  395. case float32:
  396. fright = float64(right.(float32))
  397. isInt = false
  398. case float64:
  399. fright = right.(float64)
  400. isInt = false
  401. }
  402. if isInt {
  403. return rleft - rright
  404. }
  405. return fleft + float64(rleft) - (fright + float64(rright))
  406. }
  407. // EllipsisString returns a truncated short string,
  408. // it appends '...' in the end of the length of string is too large.
  409. func EllipsisString(str string, length int) string {
  410. if length <= 3 {
  411. return "..."
  412. }
  413. if len(str) <= length {
  414. return str
  415. }
  416. return str[:length-3] + "..."
  417. }
  418. // TruncateString returns a truncated string with given limit,
  419. // it returns input string if length is not reached limit.
  420. func TruncateString(str string, limit int) string {
  421. if len(str) < limit {
  422. return str
  423. }
  424. return str[:limit]
  425. }
  426. // StringsToInt64s converts a slice of string to a slice of int64.
  427. func StringsToInt64s(strs []string) ([]int64, error) {
  428. ints := make([]int64, len(strs))
  429. for i := range strs {
  430. n, err := com.StrTo(strs[i]).Int64()
  431. if err != nil {
  432. return ints, err
  433. }
  434. ints[i] = n
  435. }
  436. return ints, nil
  437. }
  438. // Int64sToStrings converts a slice of int64 to a slice of string.
  439. func Int64sToStrings(ints []int64) []string {
  440. strs := make([]string, len(ints))
  441. for i := range ints {
  442. strs[i] = strconv.FormatInt(ints[i], 10)
  443. }
  444. return strs
  445. }
  446. // Int64sToMap converts a slice of int64 to a int64 map.
  447. func Int64sToMap(ints []int64) map[int64]bool {
  448. m := make(map[int64]bool)
  449. for _, i := range ints {
  450. m[i] = true
  451. }
  452. return m
  453. }
  454. // Int64sContains returns if a int64 in a slice of int64
  455. func Int64sContains(intsSlice []int64, a int64) bool {
  456. for _, c := range intsSlice {
  457. if c == a {
  458. return true
  459. }
  460. }
  461. return false
  462. }
  463. // IsLetter reports whether the rune is a letter (category L).
  464. // https://github.com/golang/go/blob/master/src/go/scanner/scanner.go#L257
  465. func IsLetter(ch rune) bool {
  466. return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
  467. }
  468. // IsTextFile returns true if file content format is plain text or empty.
  469. func IsTextFile(data []byte) bool {
  470. if len(data) == 0 {
  471. return true
  472. }
  473. return strings.Index(http.DetectContentType(data), "text/") != -1
  474. }
  475. // IsImageFile detects if data is an image format
  476. func IsImageFile(data []byte) bool {
  477. return strings.Index(http.DetectContentType(data), "image/") != -1
  478. }
  479. // IsPDFFile detects if data is a pdf format
  480. func IsPDFFile(data []byte) bool {
  481. return strings.Index(http.DetectContentType(data), "application/pdf") != -1
  482. }
  483. // IsVideoFile detects if data is an video format
  484. func IsVideoFile(data []byte) bool {
  485. return strings.Index(http.DetectContentType(data), "video/") != -1
  486. }