mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-29 17:36:38 +08:00
http/gateway:
- First tab at integrating @krl's new index page File icons are embedded in side icons.css (QmXB7PLRWH6bCiwrGh2MrBBjNkLv3mY3JdYXCikYZSwLED contains both the icons and bootstrap) - Fix back links (..) (fixes #1365) Thanks @JasonWoof for the insight. The back links now stop a t the root hash and work for links that do and dont end with a slash. License: MIT Signed-off-by: Henry <cryptix@riseup.net>
This commit is contained in:
@ -27,16 +27,6 @@ const (
|
|||||||
ipnsPathPrefix = "/ipns/"
|
ipnsPathPrefix = "/ipns/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// shortcut for templating
|
|
||||||
type webHandler map[string]interface{}
|
|
||||||
|
|
||||||
// struct for directory listing
|
|
||||||
type directoryItem struct {
|
|
||||||
Size uint64
|
|
||||||
Name string
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
|
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
|
||||||
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
|
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
|
||||||
type gatewayHandler struct {
|
type gatewayHandler struct {
|
||||||
@ -50,23 +40,9 @@ func newGatewayHandler(node *core.IpfsNode, conf GatewayConfig) (*gatewayHandler
|
|||||||
node: node,
|
node: node,
|
||||||
config: conf,
|
config: conf,
|
||||||
}
|
}
|
||||||
err := i.loadTemplate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the directroy list template
|
|
||||||
func (i *gatewayHandler) loadTemplate() error {
|
|
||||||
t, err := template.New("dir").Parse(listingTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.dirList = t
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(cryptix): find these helpers somewhere else
|
// TODO(cryptix): find these helpers somewhere else
|
||||||
func (i *gatewayHandler) newDagFromReader(r io.Reader) (*dag.Node, error) {
|
func (i *gatewayHandler) newDagFromReader(r io.Reader) (*dag.Node, error) {
|
||||||
// TODO(cryptix): change and remove this helper once PR1136 is merged
|
// TODO(cryptix): change and remove this helper once PR1136 is merged
|
||||||
@ -205,14 +181,36 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !foundIndex {
|
if !foundIndex {
|
||||||
// template and return directory listing
|
|
||||||
hndlr := webHandler{
|
|
||||||
"listing": dirListing,
|
|
||||||
"path": urlPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method != "HEAD" {
|
if r.Method != "HEAD" {
|
||||||
if err := i.dirList.Execute(w, hndlr); err != nil {
|
// construct the correct back link
|
||||||
|
// https://github.com/ipfs/go-ipfs/issues/1365
|
||||||
|
var backLink string = r.URL.Path
|
||||||
|
|
||||||
|
// don't go further up than /ipfs/$hash/
|
||||||
|
pathSplit := strings.Split(backLink, "/")
|
||||||
|
switch {
|
||||||
|
// keep backlink
|
||||||
|
case len(pathSplit) == 3: // url: /ipfs/$hash
|
||||||
|
|
||||||
|
// keep backlink
|
||||||
|
case len(pathSplit) == 4 && pathSplit[3] == "": // url: /ipfs/$hash/
|
||||||
|
|
||||||
|
// add the correct link depending on wether the path ends with a slash
|
||||||
|
default:
|
||||||
|
if strings.HasSuffix(backLink, "/") {
|
||||||
|
backLink += "./.."
|
||||||
|
} else {
|
||||||
|
backLink += "/.."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tplData := listingTemplateData{
|
||||||
|
Listing: dirListing,
|
||||||
|
Path: urlPath,
|
||||||
|
BackLink: backLink,
|
||||||
|
}
|
||||||
|
err := listingTemplate.Execute(w, tplData)
|
||||||
|
if err != nil {
|
||||||
internalWebError(w, err)
|
internalWebError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -441,23 +439,3 @@ func webErrorWithCode(w http.ResponseWriter, message string, err error, code int
|
|||||||
func internalWebError(w http.ResponseWriter, err error) {
|
func internalWebError(w http.ResponseWriter, err error) {
|
||||||
webErrorWithCode(w, "internalWebError", err, http.StatusInternalServerError)
|
webErrorWithCode(w, "internalWebError", err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Directory listing template
|
|
||||||
var listingTemplate = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>{{ .path }}</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Index of {{ .path }}</h2>
|
|
||||||
<ul>
|
|
||||||
<li><a href="./..">..</a></li>
|
|
||||||
{{ range .listing }}
|
|
||||||
<li><a href="{{ .Path }}">{{ .Name }}</a> - {{ .Size }} bytes</li>
|
|
||||||
{{ end }}
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
|
160
core/corehttp/gateway_indexPage.go
Normal file
160
core/corehttp/gateway_indexPage.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package corehttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// structs for directory listing
|
||||||
|
type listingTemplateData struct {
|
||||||
|
Listing []directoryItem
|
||||||
|
Path string
|
||||||
|
BackLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
type directoryItem struct {
|
||||||
|
Size uint64
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory listing template
|
||||||
|
var listingTemplate = template.Must(template.New("dir").Funcs(template.FuncMap{"iconFromExt": iconFromExt}).Parse(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<!-- TODO: seed these - maybe like the starter ex or the webui? -->
|
||||||
|
<link rel="stylesheet" href="/ipfs/QmXB7PLRWH6bCiwrGh2MrBBjNkLv3mY3JdYXCikYZSwLED/bootstrap.min.css"/>
|
||||||
|
<!-- helper to construct this is here: https://github.com/cryptix/exp/blob/master/imgesToCSSData/convert.go -->
|
||||||
|
<link rel="stylesheet" href="/ipfs/QmXB7PLRWH6bCiwrGh2MrBBjNkLv3mY3JdYXCikYZSwLED/icons.css">
|
||||||
|
<style>
|
||||||
|
.narrow {width: 0px;}
|
||||||
|
.padding { margin: 100px;}
|
||||||
|
#header {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
#logo {
|
||||||
|
height: 25px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.ipfs-icon {
|
||||||
|
width:16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>{{ .Path }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header" class="row">
|
||||||
|
<div class="col-xs-2">
|
||||||
|
<div id="logo" class="ipfs-logo"> </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Index of {{ .Path }}</strong>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<td class="narrow">
|
||||||
|
<div class="ipfs-icon ipfs-_blank"> </div>
|
||||||
|
</td>
|
||||||
|
<td class="padding">
|
||||||
|
<a href="{{.BackLink}}">..</a>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{{ range .Listing }}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="ipfs-icon {{iconFromExt .Name}}"> </div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ .Path }}">{{ .Name }}</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ .Size }} bytes</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
// helper to guess the type/icon for it by the extension name
|
||||||
|
func iconFromExt(name string) string {
|
||||||
|
ext := path.Ext(name)
|
||||||
|
_, ok := knownIcons[ext]
|
||||||
|
if !ok {
|
||||||
|
// default blank icon
|
||||||
|
return "ipfs-_blank"
|
||||||
|
}
|
||||||
|
return "ipfs-" + ext[1:] // slice of the first dot
|
||||||
|
}
|
||||||
|
|
||||||
|
var knownIcons = map[string]bool{
|
||||||
|
".aac": true,
|
||||||
|
".aiff": true,
|
||||||
|
".ai": true,
|
||||||
|
".avi": true,
|
||||||
|
".bmp": true,
|
||||||
|
".c": true,
|
||||||
|
".cpp": true,
|
||||||
|
".css": true,
|
||||||
|
".dat": true,
|
||||||
|
".dmg": true,
|
||||||
|
".doc": true,
|
||||||
|
".dotx": true,
|
||||||
|
".dwg": true,
|
||||||
|
".dxf": true,
|
||||||
|
".eps": true,
|
||||||
|
".exe": true,
|
||||||
|
".flv": true,
|
||||||
|
".gif": true,
|
||||||
|
".h": true,
|
||||||
|
".hpp": true,
|
||||||
|
".html": true,
|
||||||
|
".ics": true,
|
||||||
|
".iso": true,
|
||||||
|
".java": true,
|
||||||
|
".jpg": true,
|
||||||
|
".js": true,
|
||||||
|
".key": true,
|
||||||
|
".less": true,
|
||||||
|
".mid": true,
|
||||||
|
".mp3": true,
|
||||||
|
".mp4": true,
|
||||||
|
".mpg": true,
|
||||||
|
".odf": true,
|
||||||
|
".ods": true,
|
||||||
|
".odt": true,
|
||||||
|
".otp": true,
|
||||||
|
".ots": true,
|
||||||
|
".ott": true,
|
||||||
|
".pdf": true,
|
||||||
|
".php": true,
|
||||||
|
".png": true,
|
||||||
|
".ppt": true,
|
||||||
|
".psd": true,
|
||||||
|
".py": true,
|
||||||
|
".qt": true,
|
||||||
|
".rar": true,
|
||||||
|
".rb": true,
|
||||||
|
".rtf": true,
|
||||||
|
".sass": true,
|
||||||
|
".scss": true,
|
||||||
|
".sql": true,
|
||||||
|
".tga": true,
|
||||||
|
".tgz": true,
|
||||||
|
".tiff": true,
|
||||||
|
".txt": true,
|
||||||
|
".wav": true,
|
||||||
|
".xls": true,
|
||||||
|
".xlsx": true,
|
||||||
|
".xml": true,
|
||||||
|
".yml": true,
|
||||||
|
".zip": true,
|
||||||
|
}
|
Reference in New Issue
Block a user