Initial Codebase (untested)
This commit is contained in:
400
internal/utils.go
Normal file
400
internal/utils.go
Normal file
@@ -0,0 +1,400 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
// Blank import so we can handle image/jpeg
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
|
||||
"git.mills.io/prologic/spyda"
|
||||
"github.com/goware/urlx"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/writeas/slug"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
CacheDir = "cache"
|
||||
|
||||
requestTimeout = time.Second * 30
|
||||
|
||||
DayAgo = time.Hour * 24
|
||||
WeekAgo = DayAgo * 7
|
||||
MonthAgo = DayAgo * 30
|
||||
YearAgo = MonthAgo * 12
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadRequest = errors.New("error: request failed with non-200 response")
|
||||
)
|
||||
|
||||
func GenerateRandomToken() string {
|
||||
b := make([]byte, 16)
|
||||
rand.Read(b)
|
||||
return fmt.Sprintf("%x", b)
|
||||
}
|
||||
|
||||
func FastHash(s string) string {
|
||||
sum := blake2b.Sum256([]byte(s))
|
||||
|
||||
// Base32 is URL-safe, unlike Base64, and shorter than hex.
|
||||
encoding := base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
hash := strings.ToLower(encoding.EncodeToString(sum[:]))
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func IntPow(x, y int) int {
|
||||
return int(math.Pow(float64(x), float64(y)))
|
||||
}
|
||||
|
||||
func Slugify(uri string) string {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("Slugify(): error parsing uri: %s", uri)
|
||||
return ""
|
||||
}
|
||||
|
||||
return slug.Make(fmt.Sprintf("%s/%s", u.Hostname(), u.Path))
|
||||
}
|
||||
|
||||
func Request(conf *Config, method, url string, headers http.Header) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("%s: http.NewRequest fail: %s", url, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if headers == nil {
|
||||
headers = make(http.Header)
|
||||
}
|
||||
|
||||
// Set a default User-Agent (if none set)
|
||||
if headers.Get("User-Agent") == "" {
|
||||
headers.Set(
|
||||
"User-Agent",
|
||||
fmt.Sprintf(
|
||||
"spyda/%s (%s Support: %s)",
|
||||
spyda.FullVersion(), conf.Name, URLForPage(conf.BaseURL, "support"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
req.Header = headers
|
||||
|
||||
client := http.Client{
|
||||
Timeout: requestTimeout,
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("%s: client.Do fail: %s", url, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ResourceExists(conf *Config, url string) bool {
|
||||
res, err := Request(conf, http.MethodHead, url, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("error checking if %s exists", url)
|
||||
return false
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
return res.StatusCode/100 == 2
|
||||
}
|
||||
|
||||
func FileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CmdExists ...
|
||||
func CmdExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// RunCmd ...
|
||||
func RunCmd(timeout time.Duration, command string, args ...string) error {
|
||||
var (
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
)
|
||||
|
||||
if timeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(context.Background(), timeout)
|
||||
} else {
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
if ws, ok := exitError.Sys().(syscall.WaitStatus); ok && ws.Signal() == syscall.SIGKILL {
|
||||
err = &ErrCommandKilled{Err: err, Signal: ws.Signal()}
|
||||
} else {
|
||||
err = &ErrCommandFailed{Err: err, Status: exitError.ExitCode()}
|
||||
}
|
||||
}
|
||||
|
||||
log.
|
||||
WithError(err).
|
||||
WithField("out", string(out)).
|
||||
Errorf("error running command")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderString ...
|
||||
func RenderString(tpl string, ctx *Context) (string, error) {
|
||||
t := template.Must(template.New("tpl").Parse(tpl))
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := t.Execute(buf, ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func IsLocalURLFactory(conf *Config) func(url string) bool {
|
||||
return func(url string) bool {
|
||||
if NormalizeURL(url) == "" {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(NormalizeURL(url), NormalizeURL(conf.BaseURL))
|
||||
}
|
||||
}
|
||||
|
||||
func StringKeys(kv map[string]string) []string {
|
||||
var res []string
|
||||
for k := range kv {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func StringValues(kv map[string]string) []string {
|
||||
var res []string
|
||||
for _, v := range kv {
|
||||
res = append(res, v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func MapStrings(xs []string, f func(s string) string) []string {
|
||||
var res []string
|
||||
for _, x := range xs {
|
||||
res = append(res, f(x))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func HasString(a []string, x string) bool {
|
||||
for _, n := range a {
|
||||
if x == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func UniqStrings(xs []string) []string {
|
||||
set := make(map[string]bool)
|
||||
for _, x := range xs {
|
||||
if _, ok := set[x]; !ok {
|
||||
set[x] = true
|
||||
}
|
||||
}
|
||||
|
||||
res := []string{}
|
||||
for k := range set {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func RemoveString(xs []string, e string) []string {
|
||||
res := []string{}
|
||||
for _, x := range xs {
|
||||
if x == e {
|
||||
continue
|
||||
}
|
||||
res = append(res, x)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type URI struct {
|
||||
Type string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (u URI) IsZero() bool {
|
||||
return u.Type == "" && u.Path == ""
|
||||
}
|
||||
|
||||
func (u URI) String() string {
|
||||
return fmt.Sprintf("%s://%s", u.Type, u.Path)
|
||||
}
|
||||
|
||||
func ParseURI(uri string) (*URI, error) {
|
||||
parts := strings.Split(uri, "://")
|
||||
if len(parts) == 2 {
|
||||
return &URI{Type: strings.ToLower(parts[0]), Path: parts[1]}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid uri: %s", uri)
|
||||
}
|
||||
|
||||
func NormalizeURL(url string) string {
|
||||
if url == "" {
|
||||
return ""
|
||||
}
|
||||
u, err := urlx.Parse(url)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("error parsing url %s", url)
|
||||
return ""
|
||||
}
|
||||
if u.Scheme == "http" && strings.HasSuffix(u.Host, ":80") {
|
||||
u.Host = strings.TrimSuffix(u.Host, ":80")
|
||||
}
|
||||
if u.Scheme == "https" && strings.HasSuffix(u.Host, ":443") {
|
||||
u.Host = strings.TrimSuffix(u.Host, ":443")
|
||||
}
|
||||
u.User = nil
|
||||
u.Path = strings.TrimSuffix(u.Path, "/")
|
||||
norm, err := urlx.Normalize(u)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("error normalizing url %s", url)
|
||||
return ""
|
||||
}
|
||||
return norm
|
||||
}
|
||||
|
||||
// RedirectRefererURL constructs a Redirect URL from the given Request URL
|
||||
// and possibly Referer, if the Referer's Base URL matches the Pod's Base URL
|
||||
// will return the Referer URL otherwise the defaultURL. This is primarily used
|
||||
// to redirect a user from a successful /login back to the page they were on.
|
||||
func RedirectRefererURL(r *http.Request, conf *Config, defaultURL string) string {
|
||||
referer := NormalizeURL(r.Header.Get("Referer"))
|
||||
if referer != "" && strings.HasPrefix(referer, conf.BaseURL) {
|
||||
return referer
|
||||
}
|
||||
|
||||
return defaultURL
|
||||
}
|
||||
|
||||
func HostnameFromURL(uri string) string {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("HostnameFromURL(): error parsing url: %s", uri)
|
||||
return uri
|
||||
}
|
||||
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
func PrettyURL(uri string) string {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("PrettyURL(): error parsing url: %s", uri)
|
||||
return uri
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", u.Hostname(), strings.TrimPrefix(u.EscapedPath(), "/"))
|
||||
}
|
||||
|
||||
// IsAdminUserFactory returns a function that returns true if the user provided
|
||||
// is the configured pod administrator, false otherwise.
|
||||
func IsAdminUserFactory(conf *Config) func(user *User) bool {
|
||||
return func(user *User) bool {
|
||||
return NormalizeUsername(conf.AdminUser) == NormalizeUsername(user.Username)
|
||||
}
|
||||
}
|
||||
|
||||
func URLForPage(baseURL, page string) string {
|
||||
return fmt.Sprintf(
|
||||
"%s/%s",
|
||||
strings.TrimSuffix(baseURL, "/"),
|
||||
page,
|
||||
)
|
||||
}
|
||||
|
||||
func URLForCached(baseURL, hash string) string {
|
||||
return fmt.Sprintf(
|
||||
"%s/cached/%s",
|
||||
strings.TrimSuffix(baseURL, "/"),
|
||||
hash,
|
||||
)
|
||||
}
|
||||
|
||||
// SafeParseInt ...
|
||||
func SafeParseInt(s string, d int) int {
|
||||
n, e := strconv.Atoi(s)
|
||||
if e != nil {
|
||||
return d
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func FormatForDateTime(t time.Time) string {
|
||||
var format string
|
||||
|
||||
dt := time.Since(t)
|
||||
|
||||
if dt > YearAgo {
|
||||
format = "Mon, Jan 2 3:04PM 2006"
|
||||
} else if dt > MonthAgo {
|
||||
format = "Mon, Jan 2 3:04PM"
|
||||
} else if dt > WeekAgo {
|
||||
format = "Mon, Jan 2 3:04PM"
|
||||
} else if dt > DayAgo {
|
||||
format = "Mon 2, 3:04PM"
|
||||
} else {
|
||||
format = "3:04PM"
|
||||
}
|
||||
|
||||
return format
|
||||
}
|
||||
|
||||
// FormatRequest generates ascii representation of a request
|
||||
func FormatRequest(r *http.Request) string {
|
||||
return fmt.Sprintf(
|
||||
"%s %v %s%v %v (%s)",
|
||||
r.RemoteAddr,
|
||||
r.Method,
|
||||
r.Host,
|
||||
r.URL,
|
||||
r.Proto,
|
||||
r.UserAgent(),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user