generated from ipfs/ipfs-repository-template
-
Notifications
You must be signed in to change notification settings - Fork 114
/
Copy pathgateway.go
227 lines (192 loc) · 7.94 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
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 map[string][]string
}
// 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 (
DNSLinkHostnameKey RequestContextKey = "dnslink-hostname"
GatewayHostnameKey RequestContextKey = "gw-hostname"
ContentPathKey RequestContextKey = "content-path"
)