Skip to content

Commit

Permalink
Add "schema" key for custom protocol expansions - fix #33 (#34)
Browse files Browse the repository at this point in the history
- Also, clean up the test file structure a bit.
  • Loading branch information
issmirnov committed Dec 6, 2020
1 parent 1ad34b9 commit bf4e76f
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 77 deletions.
38 changes: 38 additions & 0 deletions c.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,41 @@ m:
expand: mail/u/2
maps:
expand: maps.google.com

z:
expand: zero.com
ssl_off: yes
zz:
expand: zero.ssl.on.com
ssl_off: no
l:
expand: localhost
ssl_off: yes
a:
port: 8080
s:
expand: service
# Wildcard expansions allow you to query specific java versions.
# Example: "ak/11/j" -> "https://kafka.apache.org/11/javadoc/index.html?overview-summary.html"
ak:
expand: kafka.apache.org
hi:
expand: contact
"*":
d:
expand: documentation.html
j:
expand: javadoc/index.html?overview-summary.html

# Note: chrome will block redirects to the "chrome://" schema. This makes sense, otherwise folks could abuse
# chrome://restart or change settings without users knowing. That said, this expansion is kept here as a reference
# on the "schema" usage. If you often need to use sftp:// or other schemas, this should work for you.
ch:
# expand: "/"
v:
expand: version # should expand to chrome://version
'n':
expand: net-internals
d:
expand: '#dns'
schema: chrome
22 changes: 17 additions & 5 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ const (
portKey = "port"
passKey = "*"
sslKey = "ssl_off"
schemaKey = "schema"
httpsPrefix = "https:/" // second slash appended in expandPath() call
httpPrefix = "http:/" // second slash appended in expandPath() call

)

// Sentinel value used to indicate set membership.
Expand All @@ -36,6 +36,17 @@ var exists = struct{}{}
// and easy test mocks.
var Afero = &afero.Afero{Fs: afero.NewOsFs()}

// parseYamlString takes a raw string and attempts to load it.
func parseYamlString(config string) (*gabs.Container, error) {
d, jsonErr := yaml.YAMLToJSON([]byte(config))
if jsonErr != nil {
fmt.Printf("Error encoding input to JSON.\n%s\n", jsonErr.Error())
return nil, jsonErr
}
j, _ := gabs.ParseJSON(d)
return j, nil
}

// parseYaml takes a file name and returns a gabs config object.
func parseYaml(fname string) (*gabs.Container, error) {
data, err := Afero.ReadFile(fname)
Expand Down Expand Up @@ -71,18 +82,19 @@ func validateConfig(c *gabs.Container) error {
// Validate all children
switch k {
case
"expand",
"query":
expandKey,
schemaKey,
queryKey:
// check that v is a string, else return error.
if _, ok := v.Data().(string); !ok {
errors = multierror.Append(errors, fmt.Errorf("expected string value for %T, got: %v", k, v.Data()))
}
case "port":
case portKey:
// check that v is a float64, else return error.
if _, ok := v.Data().(float64); !ok {
errors = multierror.Append(errors, fmt.Errorf("expected float64 value for %T, got: %v", k, v.Data()))
}
case "ssl_off":
case sslKey:
// check that v is a boolean, else return error.
if _, ok := v.Data().(bool); !ok {
errors = multierror.Append(errors, fmt.Errorf("expected bool value for %T, got: %v", k, v.Data()))
Expand Down
61 changes: 61 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"testing"

"github.com/Jeffail/gabs/v2"

. "github.com/smartystreets/goconvey/convey"
"github.com/spf13/afero"
)
Expand Down Expand Up @@ -55,6 +57,65 @@ l:
port: "not_int"
`

const cYaml = `
e:
expand: example.com
a:
expand: apples
b:
expand: bananas
g:
expand: github.com
d:
expand: issmirnov/dotfiles
z:
expand: issmirnov/zap
s:
query: "search?q="
z:
expand: zero.com
ssl_off: yes
zz:
expand: zero.ssl.on.com
ssl_off: no
l:
expand: localhost
ssl_off: yes
a:
port: 8080
s:
expand: service
ak:
expand: kafka.apache.org
hi:
expand: contact
"*":
d:
expand: documentation.html
j:
expand: javadoc/index.html?overview-summary.html
wc:
expand: wildcard.com
"*":
"*":
"*":
four:
expand: "4"
ch:
# expand: "/"
v:
expand: version # should expand to chrome://version
'n':
expand: net-internals
d:
expand: '#dns'
schema: chrome
`

func loadTestYaml() (*gabs.Container, error) {
return parseYamlString(cYaml)
}

func TestParseYaml(t *testing.T) {
Convey("Given a valid 'c.yml' file", t, func() {
Afero = &afero.Afero{Fs: afero.NewMemMapFs()}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/smartystreets/assertions v1.0.0 // indirect
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945
github.com/spf13/afero v1.2.2
golang.org/x/exp/errors v0.0.0-20200901203048-c4f52b2c50aa
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:s
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20200901203048-c4f52b2c50aa h1:i1+omYRtqpxiCaQJB4MQhUToKvMPFqUUJKvRiRp0gtE=
golang.org/x/exp/errors v0.0.0-20200901203048-c4f52b2c50aa h1:KuOC783xRi5lkhiU5v/+uXV++4UbZgc3o/STU05zqeg=
golang.org/x/exp/errors v0.0.0-20200901203048-c4f52b2c50aa/go.mod h1:YgqsNsAu4fTvlab/7uiYK9LJrCIzKg/NiZUIH1/ayqo=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
Expand Down
68 changes: 0 additions & 68 deletions main_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion text.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func getPrefix(c *gabs.Container) (string, int, error) {
return "", 0, fmt.Errorf("casting port key to float64 failed for %T:%v", p, p)
}

return "", 0, fmt.Errorf("error in config, no key matching 'expand', 'query' or 'port' in %s", c.String())
return "", 0, fmt.Errorf("error in config, no key matching 'expand', 'query', 'port' or 'schema' in %s", c.String())
}

// expandPath takes a config, list of tokens (parsed from request) and the results buffer
Expand Down Expand Up @@ -121,6 +121,7 @@ func isReserved(pathElem string) bool {
queryKey,
portKey,
passKey,
schemaKey,
sslKey:
return true
default:
Expand Down
17 changes: 14 additions & 3 deletions web.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (cw ctxWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

// IndexHandler handles all the non status expansions.
func IndexHandler(a *context, w http.ResponseWriter, r *http.Request) (int, error) {
func IndexHandler(ctx *context, w http.ResponseWriter, r *http.Request) (int, error) {
var host string
if r.Header.Get("X-Forwarded-Host") != "" {
host = r.Header.Get("X-Forwarded-Host")
Expand All @@ -51,20 +51,31 @@ func IndexHandler(a *context, w http.ResponseWriter, r *http.Request) (int, erro
var ok bool

// Check if host present in config.
children := a.config.ChildrenMap()
children := ctx.config.ChildrenMap()
if hostConfig, ok = children[host]; !ok {
return 404, fmt.Errorf("Shortcut '%s' not found in config.", host)
}

tokens := tokenize(host + r.URL.Path)

// Set up handles on token and config. We might need to skip ahead if there's a custom schema set.
tokensStart := tokens.Front()
conf := ctx.config

var path bytes.Buffer
if s := hostConfig.Path(sslKey).Data(); s != nil && s.(bool) {
path.WriteString(httpPrefix)
} else if s := hostConfig.Path(schemaKey).Data(); s != nil && s.(string) != "" {
path.WriteString(hostConfig.Path(schemaKey).Data().(string) + ":/")
// move one token ahead to parse expansions correctly.
conf = conf.ChildrenMap()[tokensStart.Value.(string)]
tokensStart = tokensStart.Next()
} else {
// Default to regular https prefix.
path.WriteString(httpsPrefix)
}

expandPath(a.config, tokens.Front(), &path)
expandPath(conf, tokensStart, &path)

// send result
http.Redirect(w, r, path.String(), http.StatusFound)
Expand Down
62 changes: 62 additions & 0 deletions web_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"net/http/httptest"
"testing"

"golang.org/x/exp/errors/fmt"

"github.com/ghodss/yaml"
. "github.com/smartystreets/goconvey/convey"
)
Expand Down Expand Up @@ -230,6 +232,66 @@ func TestIndexHandler(t *testing.T) {
})
})

Convey("When we GET http://ch/ with schema set to 'chrome' ", func() {
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
req.Host = "ch"

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

expected := "chrome://"
Convey(fmt.Sprintf("The result should be a 302 to %s", expected), func() {
So(rr.Code, ShouldEqual, http.StatusFound)
So(rr.Header().Get("Location"), ShouldEqual, expected)
})
})

Convey("When we GET http://ch/foobar with schema set to 'chrome' where 'foobar' isn't in the config ", func() {
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
req.Host = "ch"

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

expected := "chrome://foobar"
Convey(fmt.Sprintf("The result should be a 302 to %s", expected), func() {
So(rr.Code, ShouldEqual, http.StatusFound)
So(rr.Header().Get("Location"), ShouldEqual, expected)
})
})

Convey("When we GET http://ch/v with schema set to 'chrome' ", func() {
req, err := http.NewRequest("GET", "/v", nil)
So(err, ShouldBeNil)
req.Host = "ch"

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

expected := "chrome://version"
Convey(fmt.Sprintf("The result should be a 302 to %s", expected), func() {
So(rr.Code, ShouldEqual, http.StatusFound)
So(rr.Header().Get("Location"), ShouldEqual, expected)
})
})

Convey("When we GET http://ch/n/d with schema set to 'chrome' ", func() {
req, err := http.NewRequest("GET", "/n/d", nil)
So(err, ShouldBeNil)
req.Host = "ch"

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

expected := "chrome://net-internals/#dns"
Convey(fmt.Sprintf("The result should be a 302 to %s", expected), func() {
So(rr.Code, ShouldEqual, http.StatusFound)
So(rr.Header().Get("Location"), ShouldEqual, expected)
})
})

})
}

Expand Down

0 comments on commit bf4e76f

Please sign in to comment.