|
1 | 1 | package corehttp
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bufio" |
4 | 5 | "context"
|
5 | 6 | "fmt"
|
6 | 7 | "html/template"
|
@@ -270,6 +271,18 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
270 | 271 | return
|
271 | 272 | }
|
272 | 273 |
|
| 274 | + redirects, err := i.searchUpTreeForRedirects(r, parsedPath) |
| 275 | + if err == nil { |
| 276 | + redirected, err := i.redirect(w, r, redirects) |
| 277 | + if err != nil { |
| 278 | + // FIXME what to do here with errors ... |
| 279 | + } |
| 280 | + |
| 281 | + if redirected { |
| 282 | + return |
| 283 | + } |
| 284 | + } |
| 285 | + |
273 | 286 | // Resolve path to the final DAG node for the ETag
|
274 | 287 | resolvedPath, err := i.api.ResolvePath(r.Context(), parsedPath)
|
275 | 288 | switch err {
|
@@ -497,6 +510,81 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
497 | 510 | }
|
498 | 511 | }
|
499 | 512 |
|
| 513 | +type redirLine struct { |
| 514 | + matcher string |
| 515 | + to string |
| 516 | + code int |
| 517 | +} |
| 518 | + |
| 519 | +type redirs []redirLine |
| 520 | + |
| 521 | +func newRedirs(f io.Reader) *redirs { |
| 522 | + ret := redirs{} |
| 523 | + scanner := bufio.NewScanner(f) |
| 524 | + scanner.Split(bufio.ScanLines) |
| 525 | + for scanner.Scan() { |
| 526 | + groups := strings.Split(scanner.Text(), " ") |
| 527 | + if len(groups) >= 2 { |
| 528 | + matcher := groups[0] |
| 529 | + to := groups[1] |
| 530 | + // default to 302 (temporary redirect) |
| 531 | + code := 302 |
| 532 | + if len(groups) >= 3 { |
| 533 | + c, err := strconv.Atoi(groups[2]) |
| 534 | + if err == nil { |
| 535 | + code = c |
| 536 | + } |
| 537 | + } |
| 538 | + ret = append(ret, redirLine{matcher, to, code}) |
| 539 | + } |
| 540 | + } |
| 541 | + |
| 542 | + return &ret |
| 543 | +} |
| 544 | + |
| 545 | +// returns "" if no redir |
| 546 | +func (r redirs) search(path string) (string, int) { |
| 547 | + for _, rdir := range r { |
| 548 | + if path == rdir.matcher { |
| 549 | + return rdir.to, rdir.code |
| 550 | + } |
| 551 | + } |
| 552 | + |
| 553 | + return "", 0 |
| 554 | +} |
| 555 | + |
| 556 | +func (i *gatewayHandler) redirect(w http.ResponseWriter, r *http.Request, path ipath.Resolved) (bool, error) { |
| 557 | + node, err := i.api.Unixfs().Get(r.Context(), path) |
| 558 | + if err != nil { |
| 559 | + return false, fmt.Errorf("could not get redirects file: %v", err) |
| 560 | + } |
| 561 | + |
| 562 | + defer node.Close() |
| 563 | + |
| 564 | + f, ok := node.(files.File) |
| 565 | + |
| 566 | + if !ok { |
| 567 | + return false, fmt.Errorf("redirect, could not convert node to file") |
| 568 | + } |
| 569 | + |
| 570 | + redirs := newRedirs(f) |
| 571 | + |
| 572 | + // extract "file" part of URL, typically the part after /ipfs/CID/... |
| 573 | + g := strings.Split(r.URL.Path, "/") |
| 574 | + |
| 575 | + if len(g) > 3 { |
| 576 | + filePartPath := strings.Join(g[3:], "/") |
| 577 | + |
| 578 | + to, code := redirs.search(filePartPath) |
| 579 | + if code > 0 { |
| 580 | + http.Redirect(w, r, to, code) |
| 581 | + return true, nil |
| 582 | + } |
| 583 | + } |
| 584 | + |
| 585 | + return false, nil |
| 586 | +} |
| 587 | + |
500 | 588 | func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, file files.File) {
|
501 | 589 | size, err := file.Size()
|
502 | 590 | if err != nil {
|
@@ -790,6 +878,25 @@ func getFilename(s string) string {
|
790 | 878 | return gopath.Base(s)
|
791 | 879 | }
|
792 | 880 |
|
| 881 | +func (i *gatewayHandler) searchUpTreeForRedirects(r *http.Request, parsedPath ipath.Path) (ipath.Resolved, error) { |
| 882 | + pathComponents := strings.Split(parsedPath.String(), "/") |
| 883 | + |
| 884 | + for idx := len(pathComponents); idx >= 3; idx-- { |
| 885 | + rdir := gopath.Join(append(pathComponents[0:idx], "_redirects")...) |
| 886 | + rdirPath := ipath.New("/" + rdir) |
| 887 | + if rdirPath.IsValid() != nil { |
| 888 | + break |
| 889 | + } |
| 890 | + resolvedPath, err := i.api.ResolvePath(r.Context(), rdirPath) |
| 891 | + if err != nil { |
| 892 | + continue |
| 893 | + } |
| 894 | + return resolvedPath, nil |
| 895 | + } |
| 896 | + |
| 897 | + return nil, fmt.Errorf("no redirects in any parent folder") |
| 898 | +} |
| 899 | + |
793 | 900 | func (i *gatewayHandler) searchUpTreeFor404(r *http.Request, parsedPath ipath.Path) (ipath.Resolved, string, error) {
|
794 | 901 | filename404, ctype, err := preferred404Filename(r.Header.Values("Accept"))
|
795 | 902 | if err != nil {
|
|
0 commit comments