Skip to content

Commit

Permalink
add automatic hostname redirection
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Steven Allen <steven@stebalien.com>
  • Loading branch information
Stebalien committed Jan 10, 2020
1 parent 68c7df3 commit ab79a3c
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 23 deletions.
16 changes: 11 additions & 5 deletions core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,20 @@ type GatewaySpec struct {
// the gateway logic.
PathPrefixes []string

// SupportsSubdomains indicates whether or not this gateway supports
// requests of the form http://CID.ipfs.GATEWAY/...
SupportsSubdomains bool
// UseSubdomains indicates whether or not this gateway uses subdomains
// for IPFS resources instead of paths. That is: http://CID.ipfs.GATEWAY/...
//
// If this flag is set, any /ipns/$id and/or /ipfs/$id paths in PathPrefixes
// will be permanently redirected to http://$id.[ipns|ipfs].$gateway/.
//
// We do not support using both paths and subdomains for a single domain
// for security reasons.
UseSubdomains bool
}

var DefaultGatewaySpec = GatewaySpec{
PathPrefixes: []string{ipfsPathPrefix, ipnsPathPrefix, "/api/"},
SupportsSubdomains: true,
PathPrefixes: []string{ipfsPathPrefix, ipnsPathPrefix, "/api/"},
UseSubdomains: false,
}

// TODO(steb): Configurable
Expand Down
104 changes: 86 additions & 18 deletions core/corehttp/hostname.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package corehttp

import (
"context"
"fmt"
"net"
"net/http"
"strings"
Expand All @@ -21,51 +22,118 @@ func HostnameOption() ServeOption {
ctx, cancel := context.WithCancel(n.Context())
defer cancel()

// Unfortunately, many (well, ipfs.io) gateways use
// DNSLink so if we blindly rewrite with DNSLink, we'll
// break /ipfs links.
//
// We fix this by maintaining a list of known gateways
// and the paths that they serve "gateway" content on.
// That way, we can use DNSLink for everything else.
//
// TODO: We wouldn't need _any_ of this if we
// supported transparent symlink resolution on
// the gateway. If we had that, such gateways could add
// symlinks to `/ipns`, `/ipfs`, `/api`, etc. to their
// home directories. That way, `/ipfs/QmA/ipfs/QmB`
// would "just work". Should we try this?

// Is this one of our "known gateways"?
if gw, ok := KnownGateways[r.Host]; ok {
// This is a known gateway but we're not using
// the subdomain feature.

// Does this gateway _handle_ this path?
if hasPrefix(r.URL.Path, gw.PathPrefixes...) {
// It does.

// Does this gateway use subdomains?
if gw.UseSubdomains {
// Yes, redirect if applicable (pretty much everything except `/api`).
if newURL, ok := toSubdomainURL(r.Host, r.URL.Path); ok {
http.Redirect(
w, r, newURL, http.StatusMovedPermanently,
)
return
}
}
childMux.ServeHTTP(w, r)
return
}
} else if host, pathPrefix, ok := parseSubdomains(r.Host); ok {
if gw, ok := KnownGateways[host]; ok && gw.SupportsSubdomains {
// Always handle this with the gateway.
// We don't care if it's one of the
// valid path-prefixes.
// Looks like we're using subdomains.

// Again, is this a known gateway that supports subdomains?
if gw, ok := KnownGateways[host]; ok && gw.UseSubdomains {

// Yes, serve the request (and rewrite the path to not use subdomains).
r.URL.Path = pathPrefix + r.URL.Path
childMux.ServeHTTP(w, r)
return
}
}

// We don't have a known gateway. Fallback on dnslink.
host := strings.SplitN(r.Host, ":", 2)[0]
if len(host) == 0 || !isd.IsDomain(host) {
childMux.ServeHTTP(w, r)
return
}
if len(host) > 0 && isd.IsDomain(host) {
name := "/ipns/" + host
_, err := n.Namesys.Resolve(ctx, name, nsopts.Depth(1))
if err == nil || err == namesys.ErrResolveRecursion {
// The domain supports dnslink, rewrite.
r.URL.Path = name + r.URL.Path
}
} // else, just treat it as a gateway, I guess.

name := "/ipns/" + host
_, err := n.Namesys.Resolve(ctx, name, nsopts.Depth(1))
if err == nil || err == namesys.ErrResolveRecursion {
r.URL.Path = name + r.URL.Path
}
childMux.ServeHTTP(w, r)
})
return childMux, nil
}
}

func isSubdomainNamespace(ns string) bool {
switch ns {
case "ipfs", "ipns", "p2p", "ipld":
return true
default:
return false
}
}

// Parses a subdomain-based URL and returns it's components
func parseSubdomains(host string) (newHost, pathPrefix string, ok bool) {
parts := strings.SplitN(host, ".", 3)
if len(parts) < 3 {
if len(parts) < 3 || !isSubdomainNamespace(parts[1]) {
return "", "", false
}
switch parts[1] {
case "ipfs", "ipns", "p2p":
return parts[2], "/" + parts[1] + "/" + parts[0], true
return parts[2], "/" + parts[1] + "/" + parts[0], true
}

// Converts a host/path to a subdomain-based URL, if applicable.
func toSubdomainURL(host, path string) (url string, ok bool) {
parts := strings.SplitN(path, "/", 4)

var ns, object, rest string
switch len(parts) {
case 4:
rest = parts[3]
fallthrough
case 3:
ns = parts[1]
object = parts[2]
default:
return "", false
}

if !isSubdomainNamespace(ns) {
return "", false
}
return "", "", false

return fmt.Sprintf(
"http://%s.%s.%s/%s",
object,
ns,
host,
rest,
), true
}

func hasPrefix(s string, prefixes ...string) bool {
Expand Down

0 comments on commit ab79a3c

Please sign in to comment.