gitea/modules/base/tool.go

403 lines
10 KiB
Go
Raw Normal View History

2014-02-18 23:31:16 +01:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-03-07 23:22:15 +01:00
package base
2014-02-18 23:31:16 +01:00
import (
"crypto/md5"
2014-03-19 12:21:23 +01:00
"crypto/sha1"
2019-05-04 17:45:34 +02:00
"crypto/sha256"
2014-11-07 20:46:13 +01:00
"encoding/base64"
2014-02-18 23:31:16 +01:00
"encoding/hex"
"errors"
2014-03-14 07:32:11 +01:00
"fmt"
2016-02-20 23:10:05 +01:00
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
2014-03-14 07:32:11 +01:00
"time"
2016-02-20 23:10:05 +01:00
"unicode"
"unicode/utf8"
2014-05-26 02:11:25 +02:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/dustin/go-humanize"
2014-02-18 23:31:16 +01:00
)
// Use at most this many bytes to determine Content Type.
const sniffLen = 512
// SVGMimeType MIME type of SVG images.
const SVGMimeType = "image/svg+xml"
var svgTagRegex = regexp.MustCompile(`(?si)\A\s*(?:(<!--.*?-->|<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg[\s>\/]`)
var svgTagInXMLRegex = regexp.MustCompile(`(?si)\A<\?xml\b.*?\?>\s*(?:(<!--.*?-->|<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg[\s>\/]`)
2015-11-25 00:49:34 +01:00
// EncodeMD5 encodes string to md5 hex value.
func EncodeMD5(str string) string {
2014-02-18 23:31:16 +01:00
m := md5.New()
2019-06-12 21:41:28 +02:00
_, _ = m.Write([]byte(str))
2014-02-18 23:31:16 +01:00
return hex.EncodeToString(m.Sum(nil))
}
2014-03-14 07:32:11 +01:00
2016-11-24 08:17:44 +01:00
// EncodeSha1 string to sha1 hex value.
2014-11-12 12:48:50 +01:00
func EncodeSha1(str string) string {
h := sha1.New()
2019-06-12 21:41:28 +02:00
_, _ = h.Write([]byte(str))
2014-11-12 12:48:50 +01:00
return hex.EncodeToString(h.Sum(nil))
}
2019-05-04 17:45:34 +02:00
// EncodeSha256 string to sha1 hex value.
func EncodeSha256(str string) string {
h := sha256.New()
2019-06-12 21:41:28 +02:00
_, _ = h.Write([]byte(str))
2019-05-04 17:45:34 +02:00
return hex.EncodeToString(h.Sum(nil))
}
2016-11-07 23:14:50 +01:00
// ShortSha is basically just truncating.
// It is DEPRECATED and will be removed in the future.
2015-11-13 23:10:25 +01:00
func ShortSha(sha1 string) string {
return TruncateString(sha1, 10)
2015-11-13 23:10:25 +01:00
}
2016-11-24 08:17:44 +01:00
// BasicAuthDecode decode basic auth string
2014-12-10 11:10:26 +01:00
func BasicAuthDecode(encoded string) (string, string, error) {
s, err := base64.StdEncoding.DecodeString(encoded)
2014-11-07 20:46:13 +01:00
if err != nil {
2014-12-10 11:10:26 +01:00
return "", "", err
2014-11-07 20:46:13 +01:00
}
2014-12-10 11:01:17 +01:00
auth := strings.SplitN(string(s), ":", 2)
if len(auth) != 2 {
return "", "", errors.New("invalid basic authentication")
}
2014-12-10 11:10:26 +01:00
return auth[0], auth[1], nil
2014-11-07 20:46:13 +01:00
}
2016-11-24 08:17:44 +01:00
// BasicAuthEncode encode basic auth string
2014-11-07 20:46:13 +01:00
func BasicAuthEncode(username, password string) string {
return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
}
2016-11-24 08:17:44 +01:00
// VerifyTimeLimitCode verify time limit code
2014-03-19 17:50:44 +01:00
func VerifyTimeLimitCode(data string, minutes int, code string) bool {
if len(code) <= 18 {
return false
}
// split code
start := code[:12]
lives := code[12:18]
if d, err := strconv.ParseInt(lives, 10, 0); err == nil {
minutes = int(d)
2014-03-19 17:50:44 +01:00
}
// right active code
retCode := CreateTimeLimitCode(data, minutes, start)
if retCode == code && minutes > 0 {
// check time is expired or not
before, _ := time.ParseInLocation("200601021504", start, time.Local)
2014-03-19 17:50:44 +01:00
now := time.Now()
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
return true
}
}
return false
}
2016-11-24 08:17:44 +01:00
// TimeLimitCodeLength default value for time limit code
2014-03-19 17:50:44 +01:00
const TimeLimitCodeLength = 12 + 6 + 40
2016-11-24 08:17:44 +01:00
// CreateTimeLimitCode create a time limit code
2014-03-19 12:21:23 +01:00
// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string {
format := "200601021504"
2014-03-19 12:21:23 +01:00
var start, end time.Time
var startStr, endStr string
if startInf == nil {
// Use now time create code
start = time.Now()
startStr = start.Format(format)
2014-03-19 12:21:23 +01:00
} else {
// use start string create code
startStr = startInf.(string)
start, _ = time.ParseInLocation(format, startStr, time.Local)
startStr = start.Format(format)
2014-03-19 12:21:23 +01:00
}
end = start.Add(time.Minute * time.Duration(minutes))
endStr = end.Format(format)
2014-03-19 12:21:23 +01:00
// create sha1 encode string
sh := sha1.New()
_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, setting.SecretKey, startStr, endStr, minutes)))
2014-03-19 12:21:23 +01:00
encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
return code
}
2014-03-15 17:29:49 +01:00
// FileSize calculates the file size and generate user-friendly string.
2014-03-15 17:31:12 +01:00
func FileSize(s int64) string {
return humanize.IBytes(uint64(s))
}
// PrettyNumber produces a string form of the given number in base 10 with
// commas after every three orders of magnitud
func PrettyNumber(v int64) string {
return humanize.Comma(v)
2014-03-15 17:29:49 +01:00
}
2014-03-15 00:34:59 +01:00
// Subtract deals with subtraction of all types of number.
func Subtract(left interface{}, right interface{}) interface{} {
var rleft, rright int64
var fleft, fright float64
2016-11-24 08:17:44 +01:00
var isInt = true
switch v := left.(type) {
2014-03-15 00:34:59 +01:00
case int:
rleft = int64(v)
2014-03-15 00:34:59 +01:00
case int8:
rleft = int64(v)
2014-03-15 00:34:59 +01:00
case int16:
rleft = int64(v)
2014-03-15 00:34:59 +01:00
case int32:
rleft = int64(v)
2014-03-15 00:34:59 +01:00
case int64:
rleft = v
2014-03-15 00:34:59 +01:00
case float32:
fleft = float64(v)
2014-03-15 00:34:59 +01:00
isInt = false
case float64:
fleft = v
2014-03-15 00:34:59 +01:00
isInt = false
}
switch v := right.(type) {
2014-03-15 00:34:59 +01:00
case int:
rright = int64(v)
2014-03-15 00:34:59 +01:00
case int8:
rright = int64(v)
2014-03-15 00:34:59 +01:00
case int16:
rright = int64(v)
2014-03-15 00:34:59 +01:00
case int32:
rright = int64(v)
2014-03-15 00:34:59 +01:00
case int64:
rright = v
2014-03-15 00:34:59 +01:00
case float32:
fright = float64(v)
2014-03-15 00:34:59 +01:00
isInt = false
case float64:
fright = v
2014-03-15 00:34:59 +01:00
isInt = false
}
if isInt {
return rleft - rright
}
2016-11-24 08:17:44 +01:00
return fleft + float64(rleft) - (fright + float64(rright))
2014-03-15 00:34:59 +01:00
}
2015-08-10 10:52:08 +02:00
// EllipsisString returns a truncated short string,
// it appends '...' in the end of the length of string is too large.
func EllipsisString(str string, length int) string {
if length <= 3 {
return "..."
}
if utf8.RuneCountInString(str) <= length {
return str
}
return string([]rune(str)[:length-3]) + "..."
}
// TruncateString returns a truncated string with given limit,
// it returns input string if length is not reached limit.
func TruncateString(str string, limit int) string {
if utf8.RuneCountInString(str) < limit {
return str
}
return string([]rune(str)[:limit])
}
2015-08-10 10:52:08 +02:00
// StringsToInt64s converts a slice of string to a slice of int64.
func StringsToInt64s(strs []string) ([]int64, error) {
2015-08-10 10:52:08 +02:00
ints := make([]int64, len(strs))
for i := range strs {
n, err := strconv.ParseInt(strs[i], 10, 64)
if err != nil {
return ints, err
}
ints[i] = n
2015-08-10 10:52:08 +02:00
}
return ints, nil
2015-08-10 10:52:08 +02:00
}
2015-08-25 17:22:05 +02:00
// Int64sToStrings converts a slice of int64 to a slice of string.
func Int64sToStrings(ints []int64) []string {
strs := make([]string, len(ints))
for i := range ints {
strs[i] = strconv.FormatInt(ints[i], 10)
2015-08-25 17:22:05 +02:00
}
return strs
}
2015-08-10 10:52:08 +02:00
// Int64sToMap converts a slice of int64 to a int64 map.
func Int64sToMap(ints []int64) map[int64]bool {
m := make(map[int64]bool)
for _, i := range ints {
m[i] = true
}
return m
}
2016-02-20 23:10:05 +01:00
// Int64sContains returns if a int64 in a slice of int64
func Int64sContains(intsSlice []int64, a int64) bool {
for _, c := range intsSlice {
if c == a {
return true
}
}
return false
}
2016-02-20 23:10:05 +01:00
// IsLetter reports whether the rune is a letter (category L).
// https://github.com/golang/go/blob/c3b4918/src/go/scanner/scanner.go#L342
2016-02-20 23:10:05 +01:00
func IsLetter(ch rune) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
}
// DetectContentType extends http.DetectContentType with more content types.
func DetectContentType(data []byte) string {
ct := http.DetectContentType(data)
if len(data) > sniffLen {
data = data[:sniffLen]
}
if setting.UI.SVG.Enabled &&
((strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html")) && svgTagRegex.Match(data) ||
strings.Contains(ct, "text/xml") && svgTagInXMLRegex.Match(data)) {
// SVG is unsupported. https://github.com/golang/go/issues/15888
return SVGMimeType
}
return ct
}
// IsRepresentableAsText returns true if file content can be represented as
// plain text or is empty.
func IsRepresentableAsText(data []byte) bool {
return IsTextFile(data) || IsSVGImageFile(data)
}
2016-08-31 22:59:23 +02:00
// IsTextFile returns true if file content format is plain text or empty.
2016-08-30 11:08:38 +02:00
func IsTextFile(data []byte) bool {
2016-08-31 22:59:23 +02:00
if len(data) == 0 {
return true
}
return strings.Contains(DetectContentType(data), "text/")
2016-02-20 23:10:05 +01:00
}
2017-02-28 05:56:15 +01:00
// IsImageFile detects if data is an image format
2016-08-30 11:08:38 +02:00
func IsImageFile(data []byte) bool {
return strings.Contains(DetectContentType(data), "image/")
}
// IsSVGImageFile detects if data is an SVG image format
func IsSVGImageFile(data []byte) bool {
return strings.Contains(DetectContentType(data), SVGMimeType)
2016-02-20 23:10:05 +01:00
}
2017-02-28 05:56:15 +01:00
// IsPDFFile detects if data is a pdf format
2016-08-30 11:08:38 +02:00
func IsPDFFile(data []byte) bool {
return strings.Contains(DetectContentType(data), "application/pdf")
}
2017-02-28 05:56:15 +01:00
// IsVideoFile detects if data is an video format
func IsVideoFile(data []byte) bool {
return strings.Contains(DetectContentType(data), "video/")
}
// IsAudioFile detects if data is an video format
func IsAudioFile(data []byte) bool {
return strings.Contains(DetectContentType(data), "audio/")
}
// EntryIcon returns the octicon class for displaying files/directories
func EntryIcon(entry *git.TreeEntry) string {
switch {
case entry.IsLink():
te, err := entry.FollowLink()
if err != nil {
log.Debug(err.Error())
return "file-symlink-file"
}
if te.IsDir() {
return "file-submodule"
}
return "file-symlink-file"
case entry.IsDir():
return "file-directory"
case entry.IsSubModule():
return "file-submodule"
}
Add Octicon SVG spritemap (#10107) * Add octicon SVG sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Static prefix Signed-off-by: jolheiser <john.olheiser@gmail.com> * SVG for all repo icons Signed-off-by: jolheiser <john.olheiser@gmail.com> * make vendor Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap out octicons Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move octicons to top of less imports Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix JS Signed-off-by: jolheiser <john.olheiser@gmail.com> * Definitely not a search/replace Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed regex Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move to more generic calls and webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * make svg -> make webpack Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg-sprite Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Missed a test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove svg from makefile Signed-off-by: jolheiser <john.olheiser@gmail.com> * Suggestions Signed-off-by: jolheiser <john.olheiser@gmail.com> * Attempt to fix test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert timetracking test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Swap .octicon for .svg in less Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add aria-hidden Signed-off-by: jolheiser <john.olheiser@gmail.com> * Replace mega-octicon Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix webpack globbing on Windows Signed-off-by: jolheiser <john.olheiser@gmail.com> * Revert Co-Authored-By: silverwind <me@silverwind.io> * Fix octions from upstream Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix Vue and missed JS function Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add JS helper and PWA Signed-off-by: jolheiser <john.olheiser@gmail.com> * Preload SVG Signed-off-by: jolheiser <john.olheiser@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <matti@mdranta.net>
2020-02-11 18:02:41 +01:00
return "file"
}
// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
func SetupGiteaRoot() string {
giteaRoot := os.Getenv("GITEA_ROOT")
if giteaRoot == "" {
_, filename, _, _ := runtime.Caller(0)
giteaRoot = strings.TrimSuffix(filename, "modules/base/tool.go")
wd, err := os.Getwd()
if err != nil {
rel, err := filepath.Rel(giteaRoot, wd)
if err != nil && strings.HasPrefix(filepath.ToSlash(rel), "../") {
giteaRoot = wd
}
}
if _, err := os.Stat(filepath.Join(giteaRoot, "gitea")); os.IsNotExist(err) {
giteaRoot = ""
} else if err := os.Setenv("GITEA_ROOT", giteaRoot); err != nil {
giteaRoot = ""
}
}
return giteaRoot
}
// FormatNumberSI format a number
func FormatNumberSI(data interface{}) string {
var num int64
if num1, ok := data.(int64); ok {
num = num1
} else if num1, ok := data.(int); ok {
num = int64(num1)
} else {
return ""
}
if num < 1000 {
return fmt.Sprintf("%d", num)
} else if num < 1000000 {
num2 := float32(num) / float32(1000.0)
return fmt.Sprintf("%.1fk", num2)
} else if num < 1000000000 {
num2 := float32(num) / float32(1000000.0)
return fmt.Sprintf("%.1fM", num2)
}
num2 := float32(num) / float32(1000000000.0)
return fmt.Sprintf("%.1fG", num2)
}