gitea/modules/base/tool.go

447 lines
11 KiB
Go
Raw Normal View History

2014-02-18 22:31:16 +00: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 22:22:15 +00:00
package base
2014-02-18 22:31:16 +00:00
import (
"crypto/md5"
2014-03-19 11:21:23 +00:00
"crypto/sha1"
2019-05-04 15:45:34 +00:00
"crypto/sha256"
2014-11-07 19:46:13 +00:00
"encoding/base64"
2014-02-18 22:31:16 +00:00
"encoding/hex"
2014-03-14 06:32:11 +00:00
"fmt"
2016-02-20 22:10:05 +00:00
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
2014-03-14 06:32:11 +00:00
"time"
2016-02-20 22:10:05 +00:00
"unicode"
2014-05-26 00:11:25 +00:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/dustin/go-humanize"
"github.com/unknwon/com"
2014-02-18 22:31:16 +00:00
)
2015-11-24 23:49:34 +00:00
// EncodeMD5 encodes string to md5 hex value.
func EncodeMD5(str string) string {
2014-02-18 22:31:16 +00:00
m := md5.New()
2019-06-12 19:41:28 +00:00
_, _ = m.Write([]byte(str))
2014-02-18 22:31:16 +00:00
return hex.EncodeToString(m.Sum(nil))
}
2014-03-14 06:32:11 +00:00
2016-11-24 07:17:44 +00:00
// EncodeSha1 string to sha1 hex value.
2014-11-12 11:48:50 +00:00
func EncodeSha1(str string) string {
h := sha1.New()
2019-06-12 19:41:28 +00:00
_, _ = h.Write([]byte(str))
2014-11-12 11:48:50 +00:00
return hex.EncodeToString(h.Sum(nil))
}
2019-05-04 15:45:34 +00:00
// EncodeSha256 string to sha1 hex value.
func EncodeSha256(str string) string {
h := sha256.New()
2019-06-12 19:41:28 +00:00
_, _ = h.Write([]byte(str))
2019-05-04 15:45:34 +00:00
return hex.EncodeToString(h.Sum(nil))
}
2016-11-07 22:14:50 +00:00
// ShortSha is basically just truncating.
// It is DEPRECATED and will be removed in the future.
2015-11-13 22:10:25 +00:00
func ShortSha(sha1 string) string {
return TruncateString(sha1, 10)
2015-11-13 22:10:25 +00:00
}
2016-11-24 07:17:44 +00:00
// BasicAuthDecode decode basic auth string
2014-12-10 10:10:26 +00:00
func BasicAuthDecode(encoded string) (string, string, error) {
s, err := base64.StdEncoding.DecodeString(encoded)
2014-11-07 19:46:13 +00:00
if err != nil {
2014-12-10 10:10:26 +00:00
return "", "", err
2014-11-07 19:46:13 +00:00
}
2014-12-10 10:01:17 +00:00
auth := strings.SplitN(string(s), ":", 2)
2014-12-10 10:10:26 +00:00
return auth[0], auth[1], nil
2014-11-07 19:46:13 +00:00
}
2016-11-24 07:17:44 +00:00
// BasicAuthEncode encode basic auth string
2014-11-07 19:46:13 +00:00
func BasicAuthEncode(username, password string) string {
return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
}
2016-11-24 07:17:44 +00:00
// VerifyTimeLimitCode verify time limit code
2014-03-19 16:50:44 +00: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]
2014-07-26 04:24:27 +00:00
if d, err := com.StrTo(lives).Int(); err == nil {
2014-03-19 16:50:44 +00:00
minutes = d
}
// 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 16:50:44 +00:00
now := time.Now()
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
return true
}
}
return false
}
2016-11-24 07:17:44 +00:00
// TimeLimitCodeLength default value for time limit code
2014-03-19 16:50:44 +00:00
const TimeLimitCodeLength = 12 + 6 + 40
2016-11-24 07:17:44 +00:00
// CreateTimeLimitCode create a time limit code
2014-03-19 11:21:23 +00: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 11:21:23 +00: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 11:21:23 +00:00
} else {
// use start string create code
startStr = startInf.(string)
start, _ = time.ParseInLocation(format, startStr, time.Local)
startStr = start.Format(format)
2014-03-19 11:21:23 +00:00
}
end = start.Add(time.Minute * time.Duration(minutes))
endStr = end.Format(format)
2014-03-19 11:21:23 +00:00
// create sha1 encode string
sh := sha1.New()
2019-06-12 19:41:28 +00:00
_, _ = sh.Write([]byte(data + setting.SecretKey + startStr + endStr + com.ToStr(minutes)))
2014-03-19 11:21:23 +00:00
encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
return code
}
// HashEmail hashes email address to MD5 string.
// https://en.gravatar.com/site/implement/hash/
func HashEmail(email string) string {
return EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
}
2017-06-29 16:11:34 +00:00
// DefaultAvatarLink the default avatar link
2017-06-29 16:10:33 +00:00
func DefaultAvatarLink() string {
return setting.AppSubURL + "/img/avatar_default.png"
}
// DefaultAvatarSize is a sentinel value for the default avatar size, as
// determined by the avatar-hosting service.
const DefaultAvatarSize = -1
// libravatarURL returns the URL for the given email. This function should only
// be called if a federated avatar service is enabled.
func libravatarURL(email string) (*url.URL, error) {
urlStr, err := setting.LibravatarService.FromEmail(email)
if err != nil {
Better logging (#6038) (#6095) * Panic don't fatal on create new logger Fixes #5854 Signed-off-by: Andrew Thornton <art27@cantab.net> * partial broken * Update the logging infrastrcture Signed-off-by: Andrew Thornton <art27@cantab.net> * Reset the skip levels for Fatal and Error Signed-off-by: Andrew Thornton <art27@cantab.net> * broken ncsa * More log.Error fixes Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove nal * set log-levels to lowercase * Make console_test test all levels * switch to lowercased levels * OK now working * Fix vetting issues * Fix lint * Fix tests * change default logging to match current gitea * Improve log testing Signed-off-by: Andrew Thornton <art27@cantab.net> * reset error skip levels to 0 * Update documentation and access logger configuration * Redirect the router log back to gitea if redirect macaron log but also allow setting the log level - i.e. TRACE * Fix broken level caching * Refactor the router log * Add Router logger * Add colorizing options * Adjust router colors * Only create logger if they will be used * update app.ini.sample * rename Attribute ColorAttribute * Change from white to green for function * Set fatal/error levels * Restore initial trace logger * Fix Trace arguments in modules/auth/auth.go * Properly handle XORMLogger * Improve admin/config page * fix fmt * Add auto-compression of old logs * Update error log levels * Remove the unnecessary skip argument from Error, Fatal and Critical * Add stacktrace support * Fix tests * Remove x/sync from vendors? * Add stderr option to console logger * Use filepath.ToSlash to protect against Windows in tests * Remove prefixed underscores from names in colors.go * Remove not implemented database logger This was removed from Gogs on 4 Mar 2016 but left in the configuration since then. * Ensure that log paths are relative to ROOT_PATH * use path.Join * rename jsonConfig to logConfig * Rename "config" to "jsonConfig" to make it clearer * Requested changes * Requested changes: XormLogger * Try to color the windows terminal If successful default to colorizing the console logs * fixup * Colorize initially too * update vendor * Colorize logs on default and remove if this is not a colorizing logger * Fix documentation * fix test * Use go-isatty to detect if on windows we are on msys or cygwin * Fix spelling mistake * Add missing vendors * More changes * Rationalise the ANSI writer protection * Adjust colors on advice from @0x5c * Make Flags a comma separated list * Move to use the windows constant for ENABLE_VIRTUAL_TERMINAL_PROCESSING * Ensure matching is done on the non-colored message - to simpify EXPRESSION
2019-04-02 07:48:31 +00:00
log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
return nil, err
}
u, err := url.Parse(urlStr)
if err != nil {
Better logging (#6038) (#6095) * Panic don't fatal on create new logger Fixes #5854 Signed-off-by: Andrew Thornton <art27@cantab.net> * partial broken * Update the logging infrastrcture Signed-off-by: Andrew Thornton <art27@cantab.net> * Reset the skip levels for Fatal and Error Signed-off-by: Andrew Thornton <art27@cantab.net> * broken ncsa * More log.Error fixes Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove nal * set log-levels to lowercase * Make console_test test all levels * switch to lowercased levels * OK now working * Fix vetting issues * Fix lint * Fix tests * change default logging to match current gitea * Improve log testing Signed-off-by: Andrew Thornton <art27@cantab.net> * reset error skip levels to 0 * Update documentation and access logger configuration * Redirect the router log back to gitea if redirect macaron log but also allow setting the log level - i.e. TRACE * Fix broken level caching * Refactor the router log * Add Router logger * Add colorizing options * Adjust router colors * Only create logger if they will be used * update app.ini.sample * rename Attribute ColorAttribute * Change from white to green for function * Set fatal/error levels * Restore initial trace logger * Fix Trace arguments in modules/auth/auth.go * Properly handle XORMLogger * Improve admin/config page * fix fmt * Add auto-compression of old logs * Update error log levels * Remove the unnecessary skip argument from Error, Fatal and Critical * Add stacktrace support * Fix tests * Remove x/sync from vendors? * Add stderr option to console logger * Use filepath.ToSlash to protect against Windows in tests * Remove prefixed underscores from names in colors.go * Remove not implemented database logger This was removed from Gogs on 4 Mar 2016 but left in the configuration since then. * Ensure that log paths are relative to ROOT_PATH * use path.Join * rename jsonConfig to logConfig * Rename "config" to "jsonConfig" to make it clearer * Requested changes * Requested changes: XormLogger * Try to color the windows terminal If successful default to colorizing the console logs * fixup * Colorize initially too * update vendor * Colorize logs on default and remove if this is not a colorizing logger * Fix documentation * fix test * Use go-isatty to detect if on windows we are on msys or cygwin * Fix spelling mistake * Add missing vendors * More changes * Rationalise the ANSI writer protection * Adjust colors on advice from @0x5c * Make Flags a comma separated list * Move to use the windows constant for ENABLE_VIRTUAL_TERMINAL_PROCESSING * Ensure matching is done on the non-colored message - to simpify EXPRESSION
2019-04-02 07:48:31 +00:00
log.Error("Failed to parse libravatar url(%s): error %v", urlStr, err)
return nil, err
}
return u, nil
}
// SizedAvatarLink returns a sized link to the avatar for the given email
// address.
func SizedAvatarLink(email string, size int) string {
var avatarURL *url.URL
2016-08-07 18:01:47 +00:00
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
var err error
avatarURL, err = libravatarURL(email)
2017-06-29 14:30:58 +00:00
if err != nil {
2017-06-29 16:10:33 +00:00
return DefaultAvatarLink()
2017-06-29 14:30:58 +00:00
}
} else if !setting.DisableGravatar {
// copy GravatarSourceURL, because we will modify its Path.
copyOfGravatarSourceURL := *setting.GravatarSourceURL
avatarURL = &copyOfGravatarSourceURL
avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
} else {
return DefaultAvatarLink()
2016-08-07 18:01:47 +00:00
}
vals := avatarURL.Query()
vals.Set("d", "identicon")
if size != DefaultAvatarSize {
vals.Set("s", strconv.Itoa(size))
2014-03-25 16:12:27 +00:00
}
avatarURL.RawQuery = vals.Encode()
return avatarURL.String()
}
// SizedAvatarLinkWithDomain returns a sized link to the avatar for the given email
// address.
func SizedAvatarLinkWithDomain(email string, size int) string {
var avatarURL *url.URL
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
var err error
avatarURL, err = libravatarURL(email)
if err != nil {
return DefaultAvatarLink()
}
} else if !setting.DisableGravatar {
// copy GravatarSourceURL, because we will modify its Path.
copyOfGravatarSourceURL := *setting.GravatarSourceURL
avatarURL = &copyOfGravatarSourceURL
avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
} else {
return DefaultAvatarLink()
}
vals := avatarURL.Query()
vals.Set("d", "identicon")
if size != DefaultAvatarSize {
vals.Set("s", strconv.Itoa(size))
}
avatarURL.RawQuery = vals.Encode()
return avatarURL.String()
2014-03-17 04:57:18 +00:00
}
2014-03-15 16:29:49 +00:00
// FileSize calculates the file size and generate user-friendly string.
2014-03-15 16:31:12 +00: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 16:29:49 +00:00
}
2014-03-14 23:34:59 +00: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 07:17:44 +00:00
var isInt = true
switch v := left.(type) {
2014-03-14 23:34:59 +00:00
case int:
rleft = int64(v)
2014-03-14 23:34:59 +00:00
case int8:
rleft = int64(v)
2014-03-14 23:34:59 +00:00
case int16:
rleft = int64(v)
2014-03-14 23:34:59 +00:00
case int32:
rleft = int64(v)
2014-03-14 23:34:59 +00:00
case int64:
rleft = v
2014-03-14 23:34:59 +00:00
case float32:
fleft = float64(v)
2014-03-14 23:34:59 +00:00
isInt = false
case float64:
fleft = v
2014-03-14 23:34:59 +00:00
isInt = false
}
switch v := right.(type) {
2014-03-14 23:34:59 +00:00
case int:
rright = int64(v)
2014-03-14 23:34:59 +00:00
case int8:
rright = int64(v)
2014-03-14 23:34:59 +00:00
case int16:
rright = int64(v)
2014-03-14 23:34:59 +00:00
case int32:
rright = int64(v)
2014-03-14 23:34:59 +00:00
case int64:
rright = v
2014-03-14 23:34:59 +00:00
case float32:
fright = float64(v)
2014-03-14 23:34:59 +00:00
isInt = false
case float64:
fright = v
2014-03-14 23:34:59 +00:00
isInt = false
}
if isInt {
return rleft - rright
}
2016-11-24 07:17:44 +00:00
return fleft + float64(rleft) - (fright + float64(rright))
2014-03-14 23:34:59 +00:00
}
2015-08-10 08:52:08 +00: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 len(str) <= length {
return str
}
return 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 len(str) < limit {
return str
}
return str[:limit]
}
2015-08-10 08:52:08 +00:00
// StringsToInt64s converts a slice of string to a slice of int64.
func StringsToInt64s(strs []string) ([]int64, error) {
2015-08-10 08:52:08 +00:00
ints := make([]int64, len(strs))
for i := range strs {
n, err := com.StrTo(strs[i]).Int64()
if err != nil {
return ints, err
}
ints[i] = n
2015-08-10 08:52:08 +00:00
}
return ints, nil
2015-08-10 08:52:08 +00:00
}
2015-08-25 15:22:05 +00: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 15:22:05 +00:00
}
return strs
}
2015-08-10 08:52:08 +00: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 22:10:05 +00: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 22:10:05 +00:00
// IsLetter reports whether the rune is a letter (category L).
// https://github.com/golang/go/blob/master/src/go/scanner/scanner.go#L257
func IsLetter(ch rune) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
}
2016-08-31 20:59:23 +00:00
// IsTextFile returns true if file content format is plain text or empty.
2016-08-30 09:08:38 +00:00
func IsTextFile(data []byte) bool {
2016-08-31 20:59:23 +00:00
if len(data) == 0 {
return true
}
2019-06-12 19:41:28 +00:00
return strings.Contains(http.DetectContentType(data), "text/")
2016-02-20 22:10:05 +00:00
}
2017-02-28 04:56:15 +00:00
// IsImageFile detects if data is an image format
2016-08-30 09:08:38 +00:00
func IsImageFile(data []byte) bool {
2019-06-12 19:41:28 +00:00
return strings.Contains(http.DetectContentType(data), "image/")
2016-02-20 22:10:05 +00:00
}
2017-02-28 04:56:15 +00:00
// IsPDFFile detects if data is a pdf format
2016-08-30 09:08:38 +00:00
func IsPDFFile(data []byte) bool {
2019-06-12 19:41:28 +00:00
return strings.Contains(http.DetectContentType(data), "application/pdf")
}
2017-02-28 04:56:15 +00:00
// IsVideoFile detects if data is an video format
func IsVideoFile(data []byte) bool {
2019-06-12 19:41:28 +00:00
return strings.Contains(http.DetectContentType(data), "video/")
}
// IsAudioFile detects if data is an video format
func IsAudioFile(data []byte) bool {
2019-06-12 19:41:28 +00:00
return strings.Contains(http.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 17:02:41 +00: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)
}