mirror of
https://github.com/ipfs/kubo.git
synced 2026-03-12 19:57:55 +08:00
commit
224a9eb0b9
@ -23,39 +23,54 @@ var initDocPaths = []string{
|
||||
"init-doc/quick-start",
|
||||
}
|
||||
|
||||
// SeedInitDocs adds the list of embedded init documentation to the passed node, pins it and returns the root key
|
||||
func SeedInitDocs(nd *core.IpfsNode) (*key.Key, error) {
|
||||
return addAssetList(nd, initDocPaths)
|
||||
}
|
||||
|
||||
var initGwAssets = []string{
|
||||
"gw-assets/icons.css",
|
||||
"gw-assets/bootstrap.min.css",
|
||||
}
|
||||
|
||||
// SeedGatewayAssets adds the list of embedded gateway inidex assets to the passed node, pins it and returns the root key
|
||||
func SeedGatewayAssets(nd *core.IpfsNode) (*key.Key, error) {
|
||||
return addAssetList(nd, initGwAssets)
|
||||
}
|
||||
|
||||
func addAssetList(nd *core.IpfsNode, l []string) (*key.Key, error) {
|
||||
dirb := uio.NewDirectory(nd.DAG)
|
||||
|
||||
for _, p := range initDocPaths {
|
||||
for _, p := range l {
|
||||
d, err := Asset(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assets.AddDocuDir: could load Asset '%s': %s", p, err)
|
||||
return nil, fmt.Errorf("assets: could load Asset '%s': %s", p, err)
|
||||
}
|
||||
|
||||
s, err := coreunix.Add(nd, bytes.NewBuffer(d))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assets.AddDocuDir: could not Add '%s': %s", p, err)
|
||||
return nil, fmt.Errorf("assets: could not Add '%s': %s", p, err)
|
||||
}
|
||||
|
||||
fname := filepath.Base(p)
|
||||
k := key.B58KeyDecode(s)
|
||||
if err := dirb.AddChild(fname, k); err != nil {
|
||||
return nil, fmt.Errorf("assets.AddDocuDir: could not add '%s' as a child: %s", fname, err)
|
||||
return nil, fmt.Errorf("assets: could not add '%s' as a child: %s", fname, err)
|
||||
}
|
||||
}
|
||||
|
||||
dir := dirb.GetNode()
|
||||
dkey, err := nd.DAG.Add(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assets.AddDocuDir: DAG.Add(dir) failed: %s", err)
|
||||
return nil, fmt.Errorf("assets: DAG.Add(dir) failed: %s", err)
|
||||
}
|
||||
|
||||
if err := nd.Pinning.Pin(nd.Context(), dir, true); err != nil {
|
||||
return nil, fmt.Errorf("assets.AddDocuDir: Pinning on init-docu failed: %s", err)
|
||||
return nil, fmt.Errorf("assets: Pinning on init-docu failed: %s", err)
|
||||
}
|
||||
|
||||
if err := nd.Pinning.Flush(); err != nil {
|
||||
return nil, fmt.Errorf("assets.AddDocuDir: Pinnig flush failed: %s", err)
|
||||
return nil, fmt.Errorf("assets: Pinnig flush failed: %s", err)
|
||||
}
|
||||
|
||||
return &dkey, nil
|
||||
|
||||
@ -44,3 +44,34 @@ func TestEmbeddedDocs(t *testing.T) {
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestGatewayAssets(t *testing.T) {
|
||||
const wantCnt = 2
|
||||
if len(initGwAssets) < wantCnt {
|
||||
t.Fatalf("expected %d assets. got %d", wantCnt, len(initDocPaths))
|
||||
}
|
||||
|
||||
for _, f := range initGwAssets {
|
||||
// load data from filesystem (git)
|
||||
vcsData, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
t.Errorf("asset %s: could not read vcs file: %s", f, err)
|
||||
return
|
||||
}
|
||||
|
||||
// load data from emdedded source
|
||||
embdData, err := Asset(f)
|
||||
if err != nil {
|
||||
t.Errorf("asset %s: could not read vcs file: %s", f, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(vcsData, embdData) {
|
||||
t.Errorf("asset %s: vcs and embedded data isnt equal", f)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("checked %s", f)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
5
assets/gw-assets/bootstrap.min.css
vendored
Normal file
5
assets/gw-assets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
384
assets/gw-assets/icons.css
Normal file
384
assets/gw-assets/icons.css
Normal file
File diff suppressed because one or more lines are too long
@ -163,6 +163,12 @@ func addDefaultAssets(out io.Writer, repoRoot string) error {
|
||||
}
|
||||
defer nd.Close()
|
||||
|
||||
gwAkey, err := assets.SeedGatewayAssets(nd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init: seeding init docs failed: %s", err)
|
||||
}
|
||||
log.Debugf("init: seeded gateway assets %s", gwAkey)
|
||||
|
||||
dkey, err := assets.SeedInitDocs(nd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init: seeding init docs failed: %s", err)
|
||||
|
||||
@ -3,7 +3,6 @@ package corehttp
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
gopath "path"
|
||||
@ -27,22 +26,11 @@ const (
|
||||
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>)
|
||||
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
|
||||
type gatewayHandler struct {
|
||||
node *core.IpfsNode
|
||||
dirList *template.Template
|
||||
config GatewayConfig
|
||||
node *core.IpfsNode
|
||||
config GatewayConfig
|
||||
}
|
||||
|
||||
func newGatewayHandler(node *core.IpfsNode, conf GatewayConfig) (*gatewayHandler, error) {
|
||||
@ -50,23 +38,9 @@ func newGatewayHandler(node *core.IpfsNode, conf GatewayConfig) (*gatewayHandler
|
||||
node: node,
|
||||
config: conf,
|
||||
}
|
||||
err := i.loadTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
func (i *gatewayHandler) newDagFromReader(r io.Reader) (*dag.Node, error) {
|
||||
// TODO(cryptix): change and remove this helper once PR1136 is merged
|
||||
@ -205,14 +179,36 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
if !foundIndex {
|
||||
// template and return directory listing
|
||||
hndlr := webHandler{
|
||||
"listing": dirListing,
|
||||
"path": urlPath,
|
||||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
@ -441,23 +437,3 @@ func webErrorWithCode(w http.ResponseWriter, message string, err error, code int
|
||||
func internalWebError(w http.ResponseWriter, err error) {
|
||||
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,
|
||||
}
|
||||
@ -2,5 +2,6 @@
|
||||
# thus they can be defined + changed in one place
|
||||
|
||||
HASH_WELCOME_DOCS="QmVtU7ths96fMgZ8YSZAbKghyieq7AjxNdcqyVzxTt3qVe"
|
||||
HASH_GATEWAY_ASSETS="QmXB7PLRWH6bCiwrGh2MrBBjNkLv3mY3JdYXCikYZSwLED"
|
||||
HASH_HELP_PAGE="QmY5heUM5qgRubMDD1og9fhCPA6QdkMp3QCwd4s7gJsyE7"
|
||||
HASH_EMPTY_DIR="QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"
|
||||
|
||||
@ -45,9 +45,11 @@ test_expect_success "'ipfs pin rm' output looks good" '
|
||||
'
|
||||
|
||||
test_expect_success "file no longer pinned" '
|
||||
# we expect the welcome files to show up here
|
||||
# we expect the welcome files and gw assets to show up here
|
||||
echo "$HASH_WELCOME_DOCS" >expected2 &&
|
||||
ipfs refs -r "$HASH_WELCOME_DOCS" >>expected2 &&
|
||||
echo "$HASH_GATEWAY_ASSETS" >>expected2 &&
|
||||
ipfs refs -r "$HASH_GATEWAY_ASSETS" >>expected2 &&
|
||||
echo QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn >> expected2 &&
|
||||
ipfs pin ls --type=recursive --quiet >actual2 &&
|
||||
test_sort_cmp expected2 actual2
|
||||
@ -105,6 +107,7 @@ test_expect_success "adding multiblock random file succeeds" '
|
||||
test_expect_success "'ipfs pin ls --type=indirect' is correct" '
|
||||
ipfs refs "$MBLOCKHASH" >refsout &&
|
||||
ipfs refs -r "$HASH_WELCOME_DOCS" >>refsout &&
|
||||
ipfs refs -r "$HASH_GATEWAY_ASSETS" >>refsout &&
|
||||
sed -i="" "s/\(.*\)/\1 indirect/g" refsout &&
|
||||
ipfs pin ls --type=indirect >indirectpins &&
|
||||
test_sort_cmp refsout indirectpins
|
||||
@ -131,8 +134,10 @@ test_expect_success "'ipfs pin ls --type=direct' is correct" '
|
||||
test_expect_success "'ipfs pin ls --type=recursive' is correct" '
|
||||
echo "$MBLOCKHASH" >rp_expected &&
|
||||
echo "$HASH_WELCOME_DOCS" >>rp_expected &&
|
||||
echo "$HASH_GATEWAY_ASSETS" >>rp_expected &&
|
||||
echo QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn >>rp_expected &&
|
||||
ipfs refs -r "$HASH_WELCOME_DOCS" >>rp_expected &&
|
||||
ipfs refs -r "$HASH_GATEWAY_ASSETS" >>rp_expected &&
|
||||
sed -i="" "s/\(.*\)/\1 recursive/g" rp_expected &&
|
||||
ipfs pin ls --type=recursive >rp_actual &&
|
||||
test_sort_cmp rp_expected rp_actual
|
||||
|
||||
@ -53,6 +53,10 @@ test_expect_success "GET IPFS directory file output looks good" '
|
||||
test_cmp dir/test actual
|
||||
'
|
||||
|
||||
test_expect_success "GET IPFS non existent file returns code expected (404)" '
|
||||
test_curl_resp_http_code "http://127.0.0.1:$port/ipfs/$HASH2/pleaseDontAddMe" "HTTP/1.1 404 Not Found"
|
||||
'
|
||||
|
||||
test_expect_failure "GET IPNS path succeeds" '
|
||||
ipfs name publish "$HASH" &&
|
||||
NAME=$(ipfs config Identity.PeerID) &&
|
||||
|
||||
Loading…
Reference in New Issue
Block a user