Skip to content

Commit

Permalink
updated
Browse files Browse the repository at this point in the history
  • Loading branch information
jpillora committed Jul 26, 2022
1 parent 7f34da0 commit 1fd0777
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 42 deletions.
43 changes: 25 additions & 18 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,38 @@ var (
releaseRe = `(@([\w\-\.\_]{1,128}?))?`
moveRe = `(!*)`
pathRe = regexp.MustCompile(`^` + userRe + `\/` + repoRe + releaseRe + moveRe + `$`)
lettersRe = regexp.MustCompile(`[^A-Za-z0-9\ :]`)
isTermRe = regexp.MustCompile(`(?i)^(curl|wget)\/`)
isHomebrewRe = regexp.MustCompile(`(?i)^homebrew`)
errMsgRe = regexp.MustCompile(`[^A-Za-z0-9\ :]`)
errNotFound = errors.New("not found")
)

type query struct {
Timestamp time.Time
type Query struct {
User, Program, Release string
MoveToPath, SudoMove, Google, Insecure bool
Assets []asset
}

func (q query) cacheKey() string {
h := sha256.New()
q.Timestamp = time.Time{}
q.Assets = nil
if err := json.NewEncoder(h).Encode(q); err != nil {
type Result struct {
Query
Timestamp time.Time
Assets Assets
M1Asset bool
}

func (q Query) cacheKey() string {
hw := sha256.New()
jw := json.NewEncoder(hw)
if err := jw.Encode(q); err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(h.Sum(nil))
return base64.StdEncoding.EncodeToString(hw.Sum(nil))
}

// Handler serves install scripts using Github releases
type Handler struct {
Config
cacheMut sync.Mutex
cache map[string]*query
cache map[string]Result
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand All @@ -81,8 +85,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// type specific error response
showError := func(msg string, code int) {
cleaned := lettersRe.ReplaceAllString(msg, "")
if qtype == "text" {
// prevent shell injection
cleaned := errMsgRe.ReplaceAllString(msg, "")
if qtype == "script" {
cleaned = fmt.Sprintf("echo '%s'", cleaned)
}
http.Error(w, cleaned, http.StatusInternalServerError)
Expand Down Expand Up @@ -110,8 +115,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
showError("Invalid path", http.StatusBadRequest)
return
}
q := &query{
Timestamp: time.Now(),
q := &Query{
User: m[2],
Program: m[3],
Release: m[5],
Expand All @@ -132,7 +136,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
// fetch assets
if err := h.getAssets(q); err != nil {
result, err := h.execute(q)
if err != nil {
showError(err.Error(), http.StatusBadGateway)
return
}
Expand All @@ -144,7 +149,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// execute template
buff := bytes.Buffer{}
if err := t.Execute(&buff, q); err != nil {
if err := t.Execute(&buff, result); err != nil {
showError("Template error: "+err.Error(), http.StatusInternalServerError)
return
}
Expand All @@ -153,11 +158,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write(buff.Bytes())
}

type asset struct {
type Asset struct {
Name, OS, Arch, URL, Type string
Is32bit, IsMac bool
}

type Assets []Asset

func (h *Handler) get(url string, v interface{}) error {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Accept", "application/vnd.github.v3+json")
Expand Down
53 changes: 30 additions & 23 deletions handler/handler_github.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import (
"time"
)

func (h *Handler) getAssets(q *query) error {
func (h *Handler) execute(q *Query) (Result, error) {
//cached?
key := q.cacheKey()
h.cacheMut.Lock()
if h.cache == nil {
h.cache = map[string]*query{}
h.cache = map[string]Result{}
}
cq, ok := h.cache[key]
result, ok := h.cache[key]
h.cacheMut.Unlock()
if ok && time.Since(cq.Timestamp) < cacheTTL {
if ok && time.Since(result.Timestamp) < cacheTTL {
//cache hit
*q = *cq
return nil
return result, nil
}
//do real operation
err := h.getAssetsNoCache(q)
result.Timestamp = time.Now()
assets, err := h.getAssetsNoCache(q)
if err == nil {
//didn't need google
q.Google = false
Expand All @@ -39,21 +39,29 @@ func (h *Handler) getAssets(q *query) error {
q.Program = program
q.User = user
//retry assets...
err = h.getAssetsNoCache(q)
assets, err = h.getAssetsNoCache(q)
}
}
//detect if we have a native m1 asset
for _, a := range assets {
if a.OS == "darwin" && a.Arch == "arm64" {
result.M1Asset = true
break
}
}
result.Assets = assets
//asset fetch failed, dont cache
if err != nil {
return err
return result, err
}
//success store results
h.cacheMut.Lock()
h.cache[key] = q
h.cache[key] = result
h.cacheMut.Unlock()
return nil
return result, nil
}

func (h *Handler) getAssetsNoCache(q *query) error {
func (h *Handler) getAssetsNoCache(q *Query) (Assets, error) {
user := q.User
repo := q.Program
release := q.Release
Expand All @@ -68,37 +76,38 @@ func (h *Handler) getAssetsNoCache(q *query) error {
url += "/latest"
ghr := ghRelease{}
if err := h.get(url, &ghr); err != nil {
return err
return nil, err
}
q.Release = ghr.TagName //discovered
ghas = ghr.Assets
} else {
ghrs := []ghRelease{}
if err := h.get(url, &ghrs); err != nil {
return err
return nil, err
}
found := false
for _, ghr := range ghrs {
if ghr.TagName == release {
found = true
if err := h.get(ghr.AssetsURL, &ghas); err != nil {
return err
return nil, err
}
ghas = ghr.Assets
break
}
}
if !found {
return fmt.Errorf("release tag '%s' not found", release)
return nil, fmt.Errorf("release tag '%s' not found", release)
}
}
if len(ghas) == 0 {
return errors.New("no assets found")
return nil, errors.New("no assets found")
}
assets := []asset{}
assets := Assets{}

index := map[string]bool{}

//TODO: handle duplicate asset.targets
for _, ga := range ghas {
url := ga.BrowserDownloadURL
//only binary containers are supported
Expand Down Expand Up @@ -127,7 +136,7 @@ func (h *Handler) getAssetsNoCache(q *query) error {
}
index[key] = true
//include!
assets = append(assets, asset{
assets = append(assets, Asset{
//target
OS: os,
Arch: arch,
Expand All @@ -141,11 +150,9 @@ func (h *Handler) getAssetsNoCache(q *query) error {
})
}
if len(assets) == 0 {
return errors.New("no downloads found for this release")
return nil, errors.New("no downloads found for this release")
}
//TODO: handle duplicate asset.targets
q.Assets = assets
return nil
return assets, nil
}

type ghAsset struct {
Expand Down
6 changes: 5 additions & 1 deletion scripts/install.sh.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ function install {
*) fail "unknown os: $(uname -s)";;
esac
#find ARCH
if uname -m | grep 64 > /dev/null; then
{{ if .M1Asset }}if uname -m | grep arm64 > /dev/null; then
# this case only included if arm64 assets are present
# to allow fallback to amd64 (m1 rosetta TODO darwin check)
ARCH="arm64"
el{{ end }}if uname -m | grep 64 > /dev/null; then
ARCH="amd64"
elif uname -m | grep arm > /dev/null; then
ARCH="arm" #TODO armv6/v7
Expand Down

0 comments on commit 1fd0777

Please sign in to comment.