Skip to content

Commit

Permalink
- implement basic redirect
Browse files Browse the repository at this point in the history
- _redirects: add support for 200 rewrite
- add support for regex in redirects
  • Loading branch information
cbrake authored and Justin Johnson committed Apr 4, 2022
1 parent f855bfe commit 6152fa0
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 2 deletions.
80 changes: 78 additions & 2 deletions core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ func (i *gatewayHandler) optionsHandler(w http.ResponseWriter, r *http.Request)

func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) {
begin := time.Now()
urlPath := r.URL.Path

logger := log.With("from", r.RequestURI)
logger.Debug("http request received")
Expand Down Expand Up @@ -319,9 +320,25 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
}
}

contentPath := ipath.New(r.URL.Path)
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
}
}

contentPath := ipath.New(urlPath)
if pathErr := contentPath.IsValid(); pathErr != nil {
if fixupSuperfluousNamespace(w, r.URL.Path, r.URL.RawQuery) {
if fixupSuperfluousNamespace(w, urlPath, r.URL.RawQuery) {
// the error was due to redundant namespace, which we were able to fix
// by returning error/redirect page, nothing left to do here
logger.Debugw("redundant namespace; noop")
Expand Down Expand Up @@ -410,6 +427,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) servePretty404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool {
resolved404Path, ctype, err := i.searchUpTreeFor404(r, contentPath)
if err != nil {
Expand Down Expand Up @@ -809,6 +866,25 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string]
return "", nil, nil
}

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, contentPath 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)
}
}
}

0 comments on commit 6152fa0

Please sign in to comment.