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
5package base
6
7import (
8	"crypto/md5"
9	"crypto/sha1"
10	"crypto/sha256"
11	"encoding/base64"
12	"encoding/hex"
13	"errors"
14	"fmt"
15	"os"
16	"path/filepath"
17	"runtime"
18	"strconv"
19	"strings"
20	"time"
21	"unicode"
22	"unicode/utf8"
23
24	"code.gitea.io/gitea/modules/git"
25	"code.gitea.io/gitea/modules/log"
26	"code.gitea.io/gitea/modules/setting"
27
28	"github.com/dustin/go-humanize"
29)
30
31// EncodeMD5 encodes string to md5 hex value.
32func EncodeMD5(str string) string {
33	m := md5.New()
34	_, _ = m.Write([]byte(str))
35	return hex.EncodeToString(m.Sum(nil))
36}
37
38// EncodeSha1 string to sha1 hex value.
39func EncodeSha1(str string) string {
40	h := sha1.New()
41	_, _ = h.Write([]byte(str))
42	return hex.EncodeToString(h.Sum(nil))
43}
44
45// EncodeSha256 string to sha1 hex value.
46func EncodeSha256(str string) string {
47	h := sha256.New()
48	_, _ = h.Write([]byte(str))
49	return hex.EncodeToString(h.Sum(nil))
50}
51
52// ShortSha is basically just truncating.
53// It is DEPRECATED and will be removed in the future.
54func ShortSha(sha1 string) string {
55	return TruncateString(sha1, 10)
56}
57
58// BasicAuthDecode decode basic auth string
59func BasicAuthDecode(encoded string) (string, string, error) {
60	s, err := base64.StdEncoding.DecodeString(encoded)
61	if err != nil {
62		return "", "", err
63	}
64
65	auth := strings.SplitN(string(s), ":", 2)
66
67	if len(auth) != 2 {
68		return "", "", errors.New("invalid basic authentication")
69	}
70
71	return auth[0], auth[1], nil
72}
73
74// BasicAuthEncode encode basic auth string
75func BasicAuthEncode(username, password string) string {
76	return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
77}
78
79// VerifyTimeLimitCode verify time limit code
80func VerifyTimeLimitCode(data string, minutes int, code string) bool {
81	if len(code) <= 18 {
82		return false
83	}
84
85	// split code
86	start := code[:12]
87	lives := code[12:18]
88	if d, err := strconv.ParseInt(lives, 10, 0); err == nil {
89		minutes = int(d)
90	}
91
92	// right active code
93	retCode := CreateTimeLimitCode(data, minutes, start)
94	if retCode == code && minutes > 0 {
95		// check time is expired or not
96		before, _ := time.ParseInLocation("200601021504", start, time.Local)
97		now := time.Now()
98		if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
99			return true
100		}
101	}
102
103	return false
104}
105
106// TimeLimitCodeLength default value for time limit code
107const TimeLimitCodeLength = 12 + 6 + 40
108
109// CreateTimeLimitCode create a time limit code
110// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
111func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string {
112	format := "200601021504"
113
114	var start, end time.Time
115	var startStr, endStr string
116
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
128	end = start.Add(time.Minute * time.Duration(minutes))
129	endStr = end.Format(format)
130
131	// create sha1 encode string
132	sh := sha1.New()
133	_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, setting.SecretKey, startStr, endStr, minutes)))
134	encoded := hex.EncodeToString(sh.Sum(nil))
135
136	code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
137	return code
138}
139
140// FileSize calculates the file size and generate user-friendly string.
141func FileSize(s int64) string {
142	return humanize.IBytes(uint64(s))
143}
144
145// PrettyNumber produces a string form of the given number in base 10 with
146// commas after every three orders of magnitud
147func PrettyNumber(v int64) string {
148	return humanize.Comma(v)
149}
150
151// Subtract deals with subtraction of all types of number.
152func Subtract(left, right interface{}) interface{} {
153	var rleft, rright int64
154	var fleft, fright float64
155	var isInt = true
156	switch v := left.(type) {
157	case int:
158		rleft = int64(v)
159	case int8:
160		rleft = int64(v)
161	case int16:
162		rleft = int64(v)
163	case int32:
164		rleft = int64(v)
165	case int64:
166		rleft = v
167	case float32:
168		fleft = float64(v)
169		isInt = false
170	case float64:
171		fleft = v
172		isInt = false
173	}
174
175	switch v := right.(type) {
176	case int:
177		rright = int64(v)
178	case int8:
179		rright = int64(v)
180	case int16:
181		rright = int64(v)
182	case int32:
183		rright = int64(v)
184	case int64:
185		rright = v
186	case float32:
187		fright = float64(v)
188		isInt = false
189	case float64:
190		fright = v
191		isInt = false
192	}
193
194	if isInt {
195		return rleft - rright
196	}
197	return fleft + float64(rleft) - (fright + float64(rright))
198}
199
200// EllipsisString returns a truncated short string,
201// it appends '...' in the end of the length of string is too large.
202func EllipsisString(str string, length int) string {
203	if length <= 3 {
204		return "..."
205	}
206	if utf8.RuneCountInString(str) <= length {
207		return str
208	}
209	return string([]rune(str)[:length-3]) + "..."
210}
211
212// TruncateString returns a truncated string with given limit,
213// it returns input string if length is not reached limit.
214func TruncateString(str string, limit int) string {
215	if utf8.RuneCountInString(str) < limit {
216		return str
217	}
218	return string([]rune(str)[:limit])
219}
220
221// StringsToInt64s converts a slice of string to a slice of int64.
222func StringsToInt64s(strs []string) ([]int64, error) {
223	ints := make([]int64, len(strs))
224	for i := range strs {
225		n, err := strconv.ParseInt(strs[i], 10, 64)
226		if err != nil {
227			return ints, err
228		}
229		ints[i] = n
230	}
231	return ints, nil
232}
233
234// Int64sToStrings converts a slice of int64 to a slice of string.
235func Int64sToStrings(ints []int64) []string {
236	strs := make([]string, len(ints))
237	for i := range ints {
238		strs[i] = strconv.FormatInt(ints[i], 10)
239	}
240	return strs
241}
242
243// Int64sToMap converts a slice of int64 to a int64 map.
244func Int64sToMap(ints []int64) map[int64]bool {
245	m := make(map[int64]bool)
246	for _, i := range ints {
247		m[i] = true
248	}
249	return m
250}
251
252// Int64sContains returns if a int64 in a slice of int64
253func Int64sContains(intsSlice []int64, a int64) bool {
254	for _, c := range intsSlice {
255		if c == a {
256			return true
257		}
258	}
259	return false
260}
261
262// IsLetter reports whether the rune is a letter (category L).
263// https://github.com/golang/go/blob/c3b4918/src/go/scanner/scanner.go#L342
264func IsLetter(ch rune) bool {
265	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
266}
267
268// EntryIcon returns the octicon class for displaying files/directories
269func EntryIcon(entry *git.TreeEntry) string {
270	switch {
271	case entry.IsLink():
272		te, err := entry.FollowLink()
273		if err != nil {
274			log.Debug(err.Error())
275			return "file-symlink-file"
276		}
277		if te.IsDir() {
278			return "file-submodule"
279		}
280		return "file-symlink-file"
281	case entry.IsDir():
282		return "file-directory"
283	case entry.IsSubModule():
284		return "file-submodule"
285	}
286
287	return "file"
288}
289
290// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
291func SetupGiteaRoot() string {
292	giteaRoot := os.Getenv("GITEA_ROOT")
293	if giteaRoot == "" {
294		_, filename, _, _ := runtime.Caller(0)
295		giteaRoot = strings.TrimSuffix(filename, "modules/base/tool.go")
296		wd, err := os.Getwd()
297		if err != nil {
298			rel, err := filepath.Rel(giteaRoot, wd)
299			if err != nil && strings.HasPrefix(filepath.ToSlash(rel), "../") {
300				giteaRoot = wd
301			}
302		}
303		if _, err := os.Stat(filepath.Join(giteaRoot, "gitea")); os.IsNotExist(err) {
304			giteaRoot = ""
305		} else if err := os.Setenv("GITEA_ROOT", giteaRoot); err != nil {
306			giteaRoot = ""
307		}
308	}
309	return giteaRoot
310}
311
312// FormatNumberSI format a number
313func FormatNumberSI(data interface{}) string {
314	var num int64
315	if num1, ok := data.(int64); ok {
316		num = num1
317	} else if num1, ok := data.(int); ok {
318		num = int64(num1)
319	} else {
320		return ""
321	}
322
323	if num < 1000 {
324		return fmt.Sprintf("%d", num)
325	} else if num < 1000000 {
326		num2 := float32(num) / float32(1000.0)
327		return fmt.Sprintf("%.1fk", num2)
328	} else if num < 1000000000 {
329		num2 := float32(num) / float32(1000000.0)
330		return fmt.Sprintf("%.1fM", num2)
331	}
332	num2 := float32(num) / float32(1000000000.0)
333	return fmt.Sprintf("%.1fG", num2)
334}
335