Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement support for _redirects file #4

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,22 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
}
}

redirects, err := i.searchUpTreeForRedirects(r, urlPath)
if err == nil {
redirected, newPath, err := i.redirect(w, r, redirects)
if err != nil {
// FIXME what to do here with errors ...
}

if redirected {
return
}

if newPath != "" {
urlPath = newPath
}
}

parsedPath := ipath.New(urlPath)
if pathErr := parsedPath.IsValid(); pathErr != nil {
if prefix == "" && fixupSuperfluousNamespace(w, urlPath, r.URL.RawQuery) {
Expand Down Expand Up @@ -497,6 +513,46 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
}
}

// redirect returns redirected, newPath (if rewrite), error
func (i *gatewayHandler) redirect(w http.ResponseWriter, r *http.Request, path ipath.Resolved) (bool, string, error) {
node, err := i.api.Unixfs().Get(r.Context(), path)
if err != nil {
return false, "", fmt.Errorf("could not get redirects file: %v", err)
}

defer node.Close()

f, ok := node.(files.File)

if !ok {
return false, "", fmt.Errorf("redirect, could not convert node to file")
}

redirs := newRedirs(f)

// extract "file" part of URL, typically the part after /ipfs/CID/...
g := strings.Split(r.URL.Path, "/")

if len(g) > 3 {
filePartPath := "/" + strings.Join(g[3:], "/")

to, code := redirs.search(filePartPath)
if code > 0 {
if code == 200 {
// rewrite
newPath := strings.Join(g[0:3], "/") + "/" + to
return false, newPath, nil
}

// redirect
http.Redirect(w, r, to, code)
return true, "", nil
}
}

return false, "", nil
}

func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, file files.File) {
size, err := file.Size()
if err != nil {
Expand Down Expand Up @@ -790,6 +846,25 @@ func getFilename(s string) string {
return gopath.Base(s)
}

func (i *gatewayHandler) searchUpTreeForRedirects(r *http.Request, path string) (ipath.Resolved, error) {
pathComponents := strings.Split(path, "/")

for idx := len(pathComponents); idx >= 3; idx-- {
rdir := gopath.Join(append(pathComponents[0:idx], "_redirects")...)
rdirPath := ipath.New("/" + rdir)
if rdirPath.IsValid() != nil {
break
}
resolvedPath, err := i.api.ResolvePath(r.Context(), rdirPath)
if err != nil {
continue
}
return resolvedPath, nil
}

return nil, fmt.Errorf("no redirects in any parent folder")
}

func (i *gatewayHandler) searchUpTreeFor404(r *http.Request, parsedPath ipath.Path) (ipath.Resolved, string, error) {
filename404, ctype, err := preferred404Filename(r.Header.Values("Accept"))
if err != nil {
Expand Down
69 changes: 69 additions & 0 deletions core/corehttp/redirect.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package corehttp

import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"regexp"
"strconv"
"strings"

core "github.com/ipfs/go-ipfs/core"
)
Expand All @@ -26,3 +32,66 @@ type redirectHandler struct {
func (i *redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, i.path, 302)
}

type redirLine struct {
matcher string
to string
code int
}

func (rdl redirLine) match(s string) (bool, error) {
re, err := regexp.Compile(rdl.matcher)
if err != nil {
return false, fmt.Errorf("Failed to compile %v: %v", rdl.matcher, err)
}

match := re.FindString(s)
if match == "" {
return false, nil
}

return true, nil
}

type redirs []redirLine

func newRedirs(f io.Reader) *redirs {
ret := redirs{}
scanner := bufio.NewScanner(f)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
t := scanner.Text()
if len(t) > 0 && t[0] == '#' {
// comment, skip line
continue
}
groups := strings.Fields(scanner.Text())
if len(groups) >= 2 {
matcher := groups[0]
to := groups[1]
// default to 302 (temporary redirect)
code := 302
if len(groups) >= 3 {
c, err := strconv.Atoi(groups[2])
if err == nil {
code = c
}
}
ret = append(ret, redirLine{matcher, to, code})
}
}

return &ret
}

// returns "" if no redir
func (r redirs) search(path string) (string, int) {
for _, rdir := range r {
m, err := rdir.match(path)
if m && err == nil {
return rdir.to, rdir.code
}
}

return "", 0
}
37 changes: 37 additions & 0 deletions core/corehttp/redirect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package corehttp

import (
"fmt"
"testing"
)

func TestRedirline(t *testing.T) {
for _, tc := range []struct {
matcher string
s string
exp bool
errExp bool
}{
{"hi", "hi", true, false},
{"hi", "hithere", true, false},
{"^hi$", "hithere", false, false},
{"^hi$", "hi", true, false},
{"hi.*", "hithere", true, false},
{"/hi", "/hi/there", true, false},
{"^/hi/", "/hi/there/now", true, false},
{"^/hi/", "/hithere", false, false},
{"^/hi/(.*", "/hi/there/now", false, true},
} {
r := redirLine{tc.matcher, "to", 200}
ok, err := r.match(tc.s)
if ok != tc.exp {
t.Errorf("%v %v, expected %v, got %v", tc.matcher, tc.s, tc.exp,
ok)
}

if err != nil != tc.errExp {
fmt.Printf("regexp error %v\n", err)
t.Errorf("%v %v, expected error %v, got %v", tc.matcher, tc.s, tc.errExp, err == nil)
}
}
}