generated from ipfs/ipfs-repository-template
-
Notifications
You must be signed in to change notification settings - Fork 89
/
gateway.go
216 lines (181 loc) · 7.7 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
package gateway
import (
"context"
"fmt"
"io"
"net/http"
"sort"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/go-unixfs"
"github.com/ipfs/interface-go-ipfs-core/path"
)
// 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
}
// GetRange describes a range request within a UnixFS file. From and To mostly follow HTTP 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)
type GetRange 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 UnixFS file, UnixFS directory, or an IPLD block depending on what the path is that has been
// requested. Directories' files.DirEntry objects do not need to contain content, but must contain Name,
// Size, and Cid.
Get(context.Context, ImmutablePath) (ContentPathMetadata, *GetResponse, error)
// GetRange returns a full UnixFS file object. Ranges passed in are advisory for pre-fetching data, however
// consumers of this function may require extra data beyond the passed ranges (e.g. the initial bit of the file
// might be used for content type sniffing even if only the end of the file is requested).
GetRange(context.Context, ImmutablePath, ...GetRange) (ContentPathMetadata, files.File, 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"
)