aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/api/api.go
diff options
context:
space:
mode:
Diffstat (limited to 'swarm/api/api.go')
-rw-r--r--swarm/api/api.go225
1 files changed, 140 insertions, 85 deletions
diff --git a/swarm/api/api.go b/swarm/api/api.go
index d7b6d8419..7b8f04c13 100644
--- a/swarm/api/api.go
+++ b/swarm/api/api.go
@@ -16,6 +16,9 @@
package api
+//go:generate mimegen --types=./../../cmd/swarm/mimegen/mime.types --package=api --out=gen_mime.go
+//go:generate gofmt -s -w gen_mime.go
+
import (
"archive/tar"
"context"
@@ -43,7 +46,8 @@ import (
"github.com/ethereum/go-ethereum/swarm/spancontext"
"github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/storage/mru"
- opentracing "github.com/opentracing/opentracing-go"
+ "github.com/ethereum/go-ethereum/swarm/storage/mru/lookup"
+ "github.com/opentracing/opentracing-go"
)
var (
@@ -401,77 +405,54 @@ func (a *API) Get(ctx context.Context, decrypt DecryptFunc, manifestAddr storage
// we need to do some extra work if this is a mutable resource manifest
if entry.ContentType == ResourceContentType {
-
- // get the resource rootAddr
- log.Trace("resource type", "menifestAddr", manifestAddr, "hash", entry.Hash)
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- rootAddr := storage.Address(common.FromHex(entry.Hash))
- rsrc, err := a.resource.Load(ctx, rootAddr)
+ if entry.ResourceView == nil {
+ return reader, mimeType, status, nil, fmt.Errorf("Cannot decode ResourceView in manifest")
+ }
+ _, err := a.resource.Lookup(ctx, mru.NewQueryLatest(entry.ResourceView, lookup.NoClue))
if err != nil {
apiGetNotFound.Inc(1)
status = http.StatusNotFound
log.Debug(fmt.Sprintf("get resource content error: %v", err))
return reader, mimeType, status, nil, err
}
+ // get the data of the update
+ _, rsrcData, err := a.resource.GetContent(entry.ResourceView)
+ if err != nil {
+ apiGetNotFound.Inc(1)
+ status = http.StatusNotFound
+ log.Warn(fmt.Sprintf("get resource content error: %v", err))
+ return reader, mimeType, status, nil, err
+ }
- // use this key to retrieve the latest update
- params := mru.LookupLatest(rootAddr)
- rsrc, err = a.resource.Lookup(ctx, params)
+ // extract multihash
+ decodedMultihash, err := multihash.FromMultihash(rsrcData)
+ if err != nil {
+ apiGetInvalid.Inc(1)
+ status = http.StatusUnprocessableEntity
+ log.Warn("invalid resource multihash", "err", err)
+ return reader, mimeType, status, nil, err
+ }
+ manifestAddr = storage.Address(decodedMultihash)
+ log.Trace("resource is multihash", "key", manifestAddr)
+
+ // get the manifest the multihash digest points to
+ trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, NOOPDecrypt)
if err != nil {
apiGetNotFound.Inc(1)
status = http.StatusNotFound
- log.Debug(fmt.Sprintf("get resource content error: %v", err))
+ log.Warn(fmt.Sprintf("loadManifestTrie (resource multihash) error: %v", err))
return reader, mimeType, status, nil, err
}
- // if it's multihash, we will transparently serve the content this multihash points to
- // \TODO this resolve is rather expensive all in all, review to see if it can be achieved cheaper
- if rsrc.Multihash() {
-
- // get the data of the update
- _, rsrcData, err := a.resource.GetContent(rootAddr)
- if err != nil {
- apiGetNotFound.Inc(1)
- status = http.StatusNotFound
- log.Warn(fmt.Sprintf("get resource content error: %v", err))
- return reader, mimeType, status, nil, err
- }
-
- // validate that data as multihash
- decodedMultihash, err := multihash.FromMultihash(rsrcData)
- if err != nil {
- apiGetInvalid.Inc(1)
- status = http.StatusUnprocessableEntity
- log.Warn("invalid resource multihash", "err", err)
- return reader, mimeType, status, nil, err
- }
- manifestAddr = storage.Address(decodedMultihash)
- log.Trace("resource is multihash", "key", manifestAddr)
-
- // get the manifest the multihash digest points to
- trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt)
- if err != nil {
- apiGetNotFound.Inc(1)
- status = http.StatusNotFound
- log.Warn(fmt.Sprintf("loadManifestTrie (resource multihash) error: %v", err))
- return reader, mimeType, status, nil, err
- }
-
- // finally, get the manifest entry
- // it will always be the entry on path ""
- entry, _ = trie.getEntry(path)
- if entry == nil {
- status = http.StatusNotFound
- apiGetNotFound.Inc(1)
- err = fmt.Errorf("manifest (resource multihash) entry for '%s' not found", path)
- log.Trace("manifest (resource multihash) entry not found", "key", manifestAddr, "path", path)
- return reader, mimeType, status, nil, err
- }
-
- } else {
- // data is returned verbatim since it's not a multihash
- return rsrc, "application/octet-stream", http.StatusOK, nil, nil
+ // finally, get the manifest entry
+ // it will always be the entry on path ""
+ entry, _ = trie.getEntry(path)
+ if entry == nil {
+ status = http.StatusNotFound
+ apiGetNotFound.Inc(1)
+ err = fmt.Errorf("manifest (resource multihash) entry for '%s' not found", path)
+ log.Trace("manifest (resource multihash) entry not found", "key", manifestAddr, "path", path)
+ return reader, mimeType, status, nil, err
}
}
@@ -778,9 +759,14 @@ func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestP
// add the entry under the path from the request
manifestPath := path.Join(manifestPath, hdr.Name)
+ contentType := hdr.Xattrs["user.swarm.content-type"]
+ if contentType == "" {
+ contentType = mime.TypeByExtension(filepath.Ext(hdr.Name))
+ }
+ //DetectContentType("")
entry := &ManifestEntry{
Path: manifestPath,
- ContentType: hdr.Xattrs["user.swarm.content-type"],
+ ContentType: contentType,
Mode: hdr.Mode,
Size: hdr.Size,
ModTime: hdr.ModTime,
@@ -791,10 +777,15 @@ func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestP
return nil, fmt.Errorf("error adding manifest entry from tar stream: %s", err)
}
if hdr.Name == defaultPath {
+ contentType := hdr.Xattrs["user.swarm.content-type"]
+ if contentType == "" {
+ contentType = mime.TypeByExtension(filepath.Ext(hdr.Name))
+ }
+
entry := &ManifestEntry{
Hash: contentKey.Hex(),
Path: "", // default entry
- ContentType: hdr.Xattrs["user.swarm.content-type"],
+ ContentType: contentType,
Mode: hdr.Mode,
Size: hdr.Size,
ModTime: hdr.ModTime,
@@ -966,37 +957,27 @@ func (a *API) BuildDirectoryTree(ctx context.Context, mhash string, nameresolver
}
// ResourceLookup finds mutable resource updates at specific periods and versions
-func (a *API) ResourceLookup(ctx context.Context, params *mru.LookupParams) (string, []byte, error) {
- var err error
- rsrc, err := a.resource.Load(ctx, params.RootAddr())
+func (a *API) ResourceLookup(ctx context.Context, query *mru.Query) ([]byte, error) {
+ _, err := a.resource.Lookup(ctx, query)
if err != nil {
- return "", nil, err
- }
- _, err = a.resource.Lookup(ctx, params)
- if err != nil {
- return "", nil, err
+ return nil, err
}
var data []byte
- _, data, err = a.resource.GetContent(params.RootAddr())
+ _, data, err = a.resource.GetContent(&query.View)
if err != nil {
- return "", nil, err
+ return nil, err
}
- return rsrc.Name(), data, nil
-}
-
-// Create Mutable resource
-func (a *API) ResourceCreate(ctx context.Context, request *mru.Request) error {
- return a.resource.New(ctx, request)
+ return data, nil
}
// ResourceNewRequest creates a Request object to update a specific mutable resource
-func (a *API) ResourceNewRequest(ctx context.Context, rootAddr storage.Address) (*mru.Request, error) {
- return a.resource.NewUpdateRequest(ctx, rootAddr)
+func (a *API) ResourceNewRequest(ctx context.Context, view *mru.View) (*mru.Request, error) {
+ return a.resource.NewRequest(ctx, view)
}
// ResourceUpdate updates a Mutable Resource with arbitrary data.
// Upon retrieval the update will be retrieved verbatim as bytes.
-func (a *API) ResourceUpdate(ctx context.Context, request *mru.SignedResourceUpdate) (storage.Address, error) {
+func (a *API) ResourceUpdate(ctx context.Context, request *mru.Request) (storage.Address, error) {
return a.resource.Update(ctx, request)
}
@@ -1005,17 +986,91 @@ func (a *API) ResourceHashSize() int {
return a.resource.HashSize
}
-// ResolveResourceManifest retrieves the Mutable Resource manifest for the given address, and returns the address of the metadata chunk.
-func (a *API) ResolveResourceManifest(ctx context.Context, addr storage.Address) (storage.Address, error) {
+// ErrCannotLoadResourceManifest is returned when looking up a resource manifest fails
+var ErrCannotLoadResourceManifest = errors.New("Cannot load resource manifest")
+
+// ErrNotAResourceManifest is returned when the address provided returned something other than a valid manifest
+var ErrNotAResourceManifest = errors.New("Not a resource manifest")
+
+// ResolveResourceManifest retrieves the Mutable Resource manifest for the given address, and returns the Resource's view ID.
+func (a *API) ResolveResourceManifest(ctx context.Context, addr storage.Address) (*mru.View, error) {
trie, err := loadManifest(ctx, a.fileStore, addr, nil, NOOPDecrypt)
if err != nil {
- return nil, fmt.Errorf("cannot load resource manifest: %v", err)
+ return nil, ErrCannotLoadResourceManifest
}
entry, _ := trie.getEntry("")
if entry.ContentType != ResourceContentType {
- return nil, fmt.Errorf("not a resource manifest: %s", addr)
+ return nil, ErrNotAResourceManifest
+ }
+
+ return entry.ResourceView, nil
+}
+
+// ErrCannotResolveResourceURI is returned when the ENS resolver is not able to translate a name to a resource
+var ErrCannotResolveResourceURI = errors.New("Cannot resolve Resource URI")
+
+// ErrCannotResolveResourceView is returned when values provided are not enough or invalid to recreate a
+// resource view out of them.
+var ErrCannotResolveResourceView = errors.New("Cannot resolve resource view")
+
+// ResolveResourceView attempts to extract View information out of the manifest, if provided
+// If not, it attempts to extract the View out of a set of key-value pairs
+func (a *API) ResolveResourceView(ctx context.Context, uri *URI, values mru.Values) (*mru.View, error) {
+ var view *mru.View
+ var err error
+ if uri.Addr != "" {
+ // resolve the content key.
+ manifestAddr := uri.Address()
+ if manifestAddr == nil {
+ manifestAddr, err = a.Resolve(ctx, uri.Addr)
+ if err != nil {
+ return nil, ErrCannotResolveResourceURI
+ }
+ }
+
+ // get the resource view from the manifest
+ view, err = a.ResolveResourceManifest(ctx, manifestAddr)
+ if err != nil {
+ return nil, err
+ }
+ log.Debug("handle.get.resource: resolved", "manifestkey", manifestAddr, "view", view.Hex())
+ } else {
+ var v mru.View
+ if err := v.FromValues(values); err != nil {
+ return nil, ErrCannotResolveResourceView
+
+ }
+ view = &v
+ }
+ return view, nil
+}
+
+// MimeOctetStream default value of http Content-Type header
+const MimeOctetStream = "application/octet-stream"
+
+// DetectContentType by file file extension, or fallback to content sniff
+func DetectContentType(fileName string, f io.ReadSeeker) (string, error) {
+ ctype := mime.TypeByExtension(filepath.Ext(fileName))
+ if ctype != "" {
+ return ctype, nil
+ }
+
+ // save/rollback to get content probe from begin of file
+ currentPosition, err := f.Seek(0, io.SeekCurrent)
+ if err != nil {
+ return MimeOctetStream, fmt.Errorf("seeker can't seek, %s", err)
+ }
+
+ // read a chunk to decide between utf-8 text and binary
+ var buf [512]byte
+ n, _ := f.Read(buf[:])
+ ctype = http.DetectContentType(buf[:n])
+
+ _, err = f.Seek(currentPosition, io.SeekStart) // rewind to output whole file
+ if err != nil {
+ return MimeOctetStream, fmt.Errorf("seeker can't seek, %s", err)
}
- return storage.Address(common.FromHex(entry.Hash)), nil
+ return ctype, nil
}