generated from ipfs/ipfs-repository-template
-
Notifications
You must be signed in to change notification settings - Fork 117
/
Copy pathgateway.go
299 lines (253 loc) · 11.3 KB
/
gateway.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
package gateway
import (
"context"
"fmt"
"io"
"net/http"
"sort"
"github.com/ipfs/boxo/coreiface/path"
"github.com/ipfs/boxo/files"
"github.com/ipfs/boxo/ipld/unixfs"
"github.com/ipfs/go-cid"
)
// Config is the configuration used when creating a new gateway handler.
type Config struct {
// Headers is a map containing all the headers that should be sent by default
// in all requests. You can define custom headers, as well as add the recommended
// headers via AddAccessControlHeaders.
Headers map[string][]string
// DeserializedResponses configures this gateway to support returning data
// in deserialized format. By default, the gateway will only support
// trustless, verifiable [application/vnd.ipld.raw] and
// [application/vnd.ipld.car] responses, operating as a [Trustless Gateway].
//
// This global flag can be overridden per FQDN in PublicGateways map.
//
// [application/vnd.ipld.raw]: https://www.iana.org/assignments/media-types/application/vnd.ipld.raw
// [application/vnd.ipld.car]: https://www.iana.org/assignments/media-types/application/vnd.ipld.car
// [Trustless Gateway]: https://specs.ipfs.tech/http-gateways/trustless-gateway/
DeserializedResponses bool
// NoDNSLink configures the gateway to _not_ perform DNS TXT record lookups in
// response to requests with values in `Host` HTTP header. This flag can be
// overridden per FQDN in PublicGateways. To be used with WithHostname.
NoDNSLink bool
// PublicGateways configures the behavior of known public gateways. Each key is
// a fully qualified domain name (FQDN). To be used with WithHostname.
PublicGateways map[string]*Specification
}
// Specification is the specification of an IPFS Public Gateway.
type Specification struct {
// Paths is explicit list of path prefixes that should be handled by
// this gateway. Example: `["/ipfs", "/ipns"]`
// Useful if you only want to support immutable `/ipfs`.
Paths []string
// UseSubdomains indicates whether or not this is a [Subdomain Gateway].
//
// If this flag is set, any `/ipns/$id` and/or `/ipfs/$id` paths in Paths
// will be permanently redirected to `http(s)://$id.[ipns|ipfs].$gateway/`.
//
// We do not support using both paths and subdomains for a single domain
// for security reasons ([Origin isolation]).
//
// [Subdomain Gateway]: https://specs.ipfs.tech/http-gateways/subdomain-gateway/
// [Origin isolation]: https://en.wikipedia.org/wiki/Same-origin_policy
UseSubdomains bool
// NoDNSLink configures this gateway to _not_ resolve DNSLink for the
// specific FQDN provided in `Host` HTTP header. Useful when you want to
// explicitly allow or refuse hosting a single hostname. To refuse all
// DNSLinks in `Host` processing, set NoDNSLink in Config instead. This setting
// overrides the global setting.
NoDNSLink bool
// InlineDNSLink configures this gateway to always inline DNSLink names
// (FQDN) into a single DNS label in order to interop with wildcard TLS certs
// and Origin per CID isolation provided by rules like https://publicsuffix.org
//
// This should be set to true if you use HTTPS.
InlineDNSLink bool
// DeserializedResponses configures this gateway to support returning data
// in deserialized format. This setting overrides the global setting.
DeserializedResponses bool
}
// TODO: Is this what we want for ImmutablePath?
type ImmutablePath struct {
p path.Path
}
func NewImmutablePath(p path.Path) (ImmutablePath, error) {
if p.Mutable() {
return ImmutablePath{}, fmt.Errorf("path cannot be mutable")
}
return ImmutablePath{p: p}, nil
}
func (i ImmutablePath) String() string {
return i.p.String()
}
func (i ImmutablePath) Namespace() string {
return i.p.Namespace()
}
func (i ImmutablePath) Mutable() bool {
return false
}
func (i ImmutablePath) IsValid() error {
return i.p.IsValid()
}
var _ path.Path = (*ImmutablePath)(nil)
type ContentPathMetadata struct {
PathSegmentRoots []cid.Cid
LastSegment path.Resolved
ContentType string // Only used for UnixFS requests
}
// ByteRange describes a range request within a UnixFS file. From and To mostly follow [HTTP Byte Range] Request semantics.
// From >= 0 and To = nil: Get the file (From, Length)
// From >= 0 and To >= 0: Get the range (From, To)
// From >= 0 and To <0: Get the range (From, Length - To)
//
// [HTTP Byte Range]: https://httpwg.org/specs/rfc9110.html#rfc.section.14.1.2
type ByteRange struct {
From uint64
To *int64
}
type GetResponse struct {
bytes files.File
directoryMetadata *directoryMetadata
}
type directoryMetadata struct {
dagSize uint64
entries <-chan unixfs.LinkResult
}
func NewGetResponseFromFile(file files.File) *GetResponse {
return &GetResponse{bytes: file}
}
func NewGetResponseFromDirectoryListing(dagSize uint64, entries <-chan unixfs.LinkResult) *GetResponse {
return &GetResponse{directoryMetadata: &directoryMetadata{dagSize, entries}}
}
// IPFSBackend is the required set of functionality used to implement the IPFS HTTP Gateway specification.
// To signal error types to the gateway code (so that not everything is a HTTP 500) return an error wrapped with NewErrorResponse.
// There are also some existing error types that the gateway code knows how to handle (e.g. context.DeadlineExceeded
// and various IPLD pathing related errors).
type IPFSBackend interface {
// Get returns a GetResponse with UnixFS file, directory or a block in IPLD
// format e.g., (DAG-)CBOR/JSON.
//
// Returned Directories are preferably a minimum info required for enumeration: Name, Size, and Cid.
//
// Optional ranges follow [HTTP Byte Ranges] notation and can be used for
// pre-fetching specific sections of a file or a block.
//
// Range notes:
// - Generating response to a range request may require additional data
// beyond the passed ranges (e.g. a single byte range from the middle of a
// file will still need magic bytes from the very beginning for content
// type sniffing).
// - A range request for a directory currently holds no semantic meaning.
//
// [HTTP Byte Ranges]: https://httpwg.org/specs/rfc9110.html#rfc.section.14.1.2
Get(context.Context, ImmutablePath, ...ByteRange) (ContentPathMetadata, *GetResponse, error)
// GetAll returns a UnixFS file or directory depending on what the path is that has been requested. Directories should
// include all content recursively.
GetAll(context.Context, ImmutablePath) (ContentPathMetadata, files.Node, error)
// GetBlock returns a single block of data
GetBlock(context.Context, ImmutablePath) (ContentPathMetadata, files.File, error)
// Head returns a file or directory depending on what the path is that has been requested.
// For UnixFS files should return a file which has the correct file size and either returns the ContentType in ContentPathMetadata or
// enough data (e.g. 3kiB) such that the content type can be determined by sniffing.
// For all other data types returning just size information is sufficient
// TODO: give function more explicit return types
Head(context.Context, ImmutablePath) (ContentPathMetadata, files.Node, error)
// ResolvePath resolves the path using UnixFS resolver. If the path does not
// exist due to a missing link, it should return an error of type:
// NewErrorResponse(fmt.Errorf("no link named %q under %s", name, cid), http.StatusNotFound)
ResolvePath(context.Context, ImmutablePath) (ContentPathMetadata, error)
// GetCAR returns a CAR file for the given immutable path
// Returns an initial error if there was an issue before the CAR streaming begins as well as a channel with a single
// that may contain a single error for if any errors occur during the streaming. If there was an initial error the
// error channel is nil
// TODO: Make this function signature better
GetCAR(context.Context, ImmutablePath) (ContentPathMetadata, io.ReadCloser, <-chan error, error)
// IsCached returns whether or not the path exists locally.
IsCached(context.Context, path.Path) bool
// GetIPNSRecord retrieves the best IPNS record for a given CID (libp2p-key)
// from the routing system.
GetIPNSRecord(context.Context, cid.Cid) ([]byte, error)
// ResolveMutable takes a mutable path and resolves it into an immutable one. This means recursively resolving any
// DNSLink or IPNS records.
//
// For example, given a mapping from `/ipns/dnslink.tld -> /ipns/ipns-id/mydirectory` and `/ipns/ipns-id` to
// `/ipfs/some-cid`, the result of passing `/ipns/dnslink.tld/myfile` would be `/ipfs/some-cid/mydirectory/myfile`.
ResolveMutable(context.Context, path.Path) (ImmutablePath, error)
// GetDNSLinkRecord returns the DNSLink TXT record for the provided FQDN.
// Unlike ResolvePath, it does not perform recursive resolution. It only
// checks for the existence of a DNSLink TXT record with path starting with
// /ipfs/ or /ipns/ and returns the path as-is.
GetDNSLinkRecord(context.Context, string) (path.Path, error)
}
// A helper function to clean up a set of headers:
// 1. Canonicalizes.
// 2. Deduplicates.
// 3. Sorts.
func cleanHeaderSet(headers []string) []string {
// Deduplicate and canonicalize.
m := make(map[string]struct{}, len(headers))
for _, h := range headers {
m[http.CanonicalHeaderKey(h)] = struct{}{}
}
result := make([]string, 0, len(m))
for k := range m {
result = append(result, k)
}
// Sort
sort.Strings(result)
return result
}
// AddAccessControlHeaders adds default headers used for controlling
// cross-origin requests. This function adds several values to the
// Access-Control-Allow-Headers and Access-Control-Expose-Headers entries.
// If the Access-Control-Allow-Origin entry is missing a value of '*' is
// added, indicating that browsers should allow requesting code from any
// origin to access the resource.
// If the Access-Control-Allow-Methods entry is missing a value of 'GET' is
// added, indicating that browsers may use the GET method when issuing cross
// origin requests.
func AddAccessControlHeaders(headers map[string][]string) {
// Hard-coded headers.
const ACAHeadersName = "Access-Control-Allow-Headers"
const ACEHeadersName = "Access-Control-Expose-Headers"
const ACAOriginName = "Access-Control-Allow-Origin"
const ACAMethodsName = "Access-Control-Allow-Methods"
if _, ok := headers[ACAOriginName]; !ok {
// Default to *all*
headers[ACAOriginName] = []string{"*"}
}
if _, ok := headers[ACAMethodsName]; !ok {
// Default to GET
headers[ACAMethodsName] = []string{http.MethodGet}
}
headers[ACAHeadersName] = cleanHeaderSet(
append([]string{
"Content-Type",
"User-Agent",
"Range",
"X-Requested-With",
}, headers[ACAHeadersName]...))
headers[ACEHeadersName] = cleanHeaderSet(
append([]string{
"Content-Length",
"Content-Range",
"X-Chunked-Output",
"X-Stream-Output",
"X-Ipfs-Path",
"X-Ipfs-Roots",
}, headers[ACEHeadersName]...))
}
type RequestContextKey string
const (
// GatewayHostnameKey is the key for the hostname at which the gateway is
// operating. It may be a DNSLink, Subdomain or Regular gateway.
GatewayHostnameKey RequestContextKey = "gw-hostname"
// DNSLinkHostnameKey is the key for the hostname of a DNSLink Gateway:
// https://specs.ipfs.tech/http-gateways/dnslink-gateway/
DNSLinkHostnameKey RequestContextKey = "dnslink-hostname"
// SubdomainHostnameKey is the key for the hostname of a Subdomain Gateway:
// https://specs.ipfs.tech/http-gateways/subdomain-gateway/
SubdomainHostnameKey RequestContextKey = "subdomain-hostname"
ContentPathKey RequestContextKey = "content-path"
)