Skip to content

Commit 1ae30bd

Browse files
committed
fix(core/gateway): option to limit directory size listing
1 parent 7871a0b commit 1ae30bd

File tree

10 files changed

+91
-23
lines changed

10 files changed

+91
-23
lines changed

assets/bindata.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/bindata_version_hash.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
package assets
33

44
const (
5-
BindataVersionHash = "512eb789cd905714e03f29d4e04de7549e8c9c3e"
5+
BindataVersionHash = "e127d173ca7ba0b94d6b700016066bda84509e87"
66
)

assets/dir-index-html/dir-index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@
5353
{{ .Hash }}
5454
</div>
5555
{{ end }}
56+
{{ if .WarnMaxDirectorySize }}
57+
<div>
58+
{{ .WarnMaxDirectorySize }}
59+
</div>
60+
{{ end }}
5661
</div>
5762
{{ if .Size }}
5863
<div class="no-linebreak flex-shrink-1 ml-auto">

assets/dir-index-html/src/dir-index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
{{ .Hash }}
5353
</div>
5454
{{ end }}
55+
{{ if .WarnMaxDirectorySize }}
56+
<div>
57+
{{ .WarnMaxDirectorySize }}
58+
</div>
59+
{{ end }}
5560
</div>
5661
{{ if .Size }}
5762
<div class="no-linebreak flex-shrink-1 ml-auto">

config/gateway.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ type Gateway struct {
5353
// }
5454
PathPrefixes []string
5555

56+
// MaxDirectorySize is the maximum directory size that will be listed in the
57+
// gateway. A directory being listed with a detected size above this threshold
58+
// will be stopped to avoid performance downgrade while fetching entries'
59+
// metadata. The default value of 0 means no limit will be applied.
60+
MaxDirectorySize OptionalInteger `json:",omitempty"`
61+
5662
// FIXME: Not yet implemented
5763
APICommands []string
5864

core/coreapi/unixfs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ func (api *UnixfsAPI) processLink(ctx context.Context, linkres ft.LinkResult, se
302302
}
303303

304304
func (api *UnixfsAPI) lsFromLinksAsync(ctx context.Context, dir uio.Directory, settings *options.UnixfsLsSettings) (<-chan coreiface.DirEntry, error) {
305-
out := make(chan coreiface.DirEntry)
305+
out := make(chan coreiface.DirEntry, uio.DefaultShardWidth)
306306

307307
go func() {
308308
defer close(out)

core/corehttp/gateway.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import (
1616
)
1717

1818
type GatewayConfig struct {
19-
Headers map[string][]string
20-
Writable bool
21-
PathPrefixes []string
19+
Headers map[string][]string
20+
Writable bool
21+
PathPrefixes []string
22+
MaxDirectorySize int
2223
}
2324

2425
// A helper function to clean up a set of headers:
@@ -89,9 +90,10 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
8990
}, headers[ACEHeadersName]...))
9091

9192
var gateway http.Handler = newGatewayHandler(GatewayConfig{
92-
Headers: headers,
93-
Writable: writable,
94-
PathPrefixes: cfg.Gateway.PathPrefixes,
93+
Headers: headers,
94+
Writable: writable,
95+
PathPrefixes: cfg.Gateway.PathPrefixes,
96+
MaxDirectorySize: int(cfg.Gateway.MaxDirectorySize.WithDefault(0)),
9597
}, api)
9698

9799
gateway = otelhttp.NewHandler(gateway, "Gateway.Request")

core/corehttp/gateway_handler_unixfs.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package corehttp
22

33
import (
4+
"context"
45
"fmt"
56
"html"
67
"net/http"
@@ -9,6 +10,7 @@ import (
910
files "github.com/ipfs/go-ipfs-files"
1011
"github.com/ipfs/go-ipfs/tracing"
1112
ipath "github.com/ipfs/interface-go-ipfs-core/path"
13+
"github.com/ipfs/interface-go-ipfs-core/options"
1214
"go.opentelemetry.io/otel/attribute"
1315
"go.opentelemetry.io/otel/trace"
1416
"go.uber.org/zap"
@@ -17,8 +19,14 @@ import (
1719
func (i *gatewayHandler) serveUnixFs(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) {
1820
ctx, span := tracing.Span(r.Context(), "Gateway", "ServeUnixFs", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
1921
defer span.End()
22+
23+
// Decouple (potential) directory context to cancel it in case it's too big.
24+
directoryContext, cancelDirContext := context.WithCancel(ctx)
25+
2026
// Handling UnixFS
21-
dr, err := i.api.Unixfs().Get(ctx, resolvedPath)
27+
// FIXME: We should be using `Unixfs().Ls()` not only here but as much as
28+
// possible in this function.
29+
dr, err := i.api.Unixfs().Get(directoryContext, resolvedPath)
2230
if err != nil {
2331
webError(w, "ipfs cat "+html.EscapeString(contentPath.String()), err, http.StatusNotFound)
2432
return
@@ -38,6 +46,38 @@ func (i *gatewayHandler) serveUnixFs(w http.ResponseWriter, r *http.Request, res
3846
internalWebError(w, fmt.Errorf("unsupported UnixFs type"))
3947
return
4048
}
49+
50+
// Preemptively count directory entries to stop listing directories too big.
51+
directoryTooBig := make(chan struct{})
52+
if i.config.MaxDirectorySize > 0 {
53+
dirEntryChan, err := i.api.Unixfs().Ls(ctx, resolvedPath, options.Unixfs.ResolveChildren(false))
54+
if err != nil {
55+
internalWebError(w, err)
56+
return
57+
}
58+
directoryEntriesNumber := 0
59+
go func() {
60+
for {
61+
select {
62+
case <-ctx.Done():
63+
return
64+
default:
65+
}
66+
select {
67+
case <-ctx.Done():
68+
return
69+
case <-dirEntryChan:
70+
directoryEntriesNumber++
71+
if directoryEntriesNumber >= i.config.MaxDirectorySize {
72+
close(directoryTooBig)
73+
cancelDirContext()
74+
return
75+
}
76+
}
77+
}
78+
}()
79+
}
80+
4181
logger.Debugw("serving unixfs directory", "path", contentPath)
42-
i.serveDirectory(w, r, resolvedPath, contentPath, dir, begin, logger)
82+
i.serveDirectory(w, r, resolvedPath, contentPath, dir, begin, logger, directoryTooBig)
4383
}

core/corehttp/gateway_handler_unixfs_dir.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package corehttp
22

33
import (
4+
"fmt"
45
"net/http"
56
"net/url"
67
gopath "path"
@@ -22,7 +23,7 @@ import (
2223
// serveDirectory returns the best representation of UnixFS directory
2324
//
2425
// It will return index.html if present, or generate directory listing otherwise.
25-
func (i *gatewayHandler) serveDirectory(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, dir files.Directory, begin time.Time, logger *zap.SugaredLogger) {
26+
func (i *gatewayHandler) serveDirectory(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, dir files.Directory, begin time.Time, logger *zap.SugaredLogger, directoryTooBig <-chan struct{}) {
2627
ctx, span := tracing.Span(r.Context(), "Gateway", "ServeDirectory", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
2728
defer span.End()
2829

@@ -133,9 +134,16 @@ func (i *gatewayHandler) serveDirectory(w http.ResponseWriter, r *http.Request,
133134
}
134135
dirListing = append(dirListing, di)
135136
}
137+
var warnMaxDirectorySize string
136138
if dirit.Err() != nil {
137-
internalWebError(w, dirit.Err())
138-
return
139+
select {
140+
case <-directoryTooBig:
141+
warnMaxDirectorySize = fmt.Sprintf("Directories bigger than %d items can't be rendered fully. Try using the CLI: ipfs ls -s --size=false --resolve-type=false %s",
142+
i.config.MaxDirectorySize, resolvedPath.Cid().String())
143+
default:
144+
internalWebError(w, dirit.Err())
145+
return
146+
}
139147
}
140148

141149
// construct the correct back link
@@ -192,6 +200,7 @@ func (i *gatewayHandler) serveDirectory(w http.ResponseWriter, r *http.Request,
192200
Breadcrumbs: breadcrumbs(contentPath.String(), dnslink),
193201
BackLink: backLink,
194202
Hash: hash,
203+
WarnMaxDirectorySize: warnMaxDirectorySize,
195204
}
196205

197206
logger.Debugw("request processed", "tplDataDNSLink", dnslink, "tplDataSize", size, "tplDataBackLink", backLink, "tplDataHash", hash)

core/corehttp/gateway_indexPage.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import (
1212

1313
// structs for directory listing
1414
type listingTemplateData struct {
15-
GatewayURL string
16-
DNSLink bool
17-
Listing []directoryItem
18-
Size string
19-
Path string
20-
Breadcrumbs []breadcrumb
21-
BackLink string
22-
Hash string
15+
GatewayURL string
16+
DNSLink bool
17+
Listing []directoryItem
18+
Size string
19+
Path string
20+
Breadcrumbs []breadcrumb
21+
BackLink string
22+
Hash string
23+
WarnMaxDirectorySize string
2324
}
2425

2526
type directoryItem struct {

0 commit comments

Comments
 (0)