diff options
Diffstat (limited to 'swarm/api')
29 files changed, 0 insertions, 10122 deletions
diff --git a/swarm/api/act.go b/swarm/api/act.go deleted file mode 100644 index a79f1944b..000000000 --- a/swarm/api/act.go +++ /dev/null @@ -1,538 +0,0 @@ -package api - -import ( - "context" - "crypto/ecdsa" - "crypto/rand" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "strings" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/sctx" - "github.com/ethereum/go-ethereum/swarm/storage" - "golang.org/x/crypto/scrypt" - "golang.org/x/crypto/sha3" - cli "gopkg.in/urfave/cli.v1" -) - -var ( - ErrDecrypt = errors.New("cant decrypt - forbidden") - ErrUnknownAccessType = errors.New("unknown access type (or not implemented)") - ErrDecryptDomainForbidden = errors.New("decryption request domain forbidden - can only decrypt on localhost") - AllowedDecryptDomains = []string{ - "localhost", - "127.0.0.1", - } -) - -const EmptyCredentials = "" - -type AccessEntry struct { - Type AccessType - Publisher string - Salt []byte - Act string - KdfParams *KdfParams -} - -type DecryptFunc func(*ManifestEntry) error - -func (a *AccessEntry) MarshalJSON() (out []byte, err error) { - - return json.Marshal(struct { - Type AccessType `json:"type,omitempty"` - Publisher string `json:"publisher,omitempty"` - Salt string `json:"salt,omitempty"` - Act string `json:"act,omitempty"` - KdfParams *KdfParams `json:"kdf_params,omitempty"` - }{ - Type: a.Type, - Publisher: a.Publisher, - Salt: hex.EncodeToString(a.Salt), - Act: a.Act, - KdfParams: a.KdfParams, - }) - -} - -func (a *AccessEntry) UnmarshalJSON(value []byte) error { - v := struct { - Type AccessType `json:"type,omitempty"` - Publisher string `json:"publisher,omitempty"` - Salt string `json:"salt,omitempty"` - Act string `json:"act,omitempty"` - KdfParams *KdfParams `json:"kdf_params,omitempty"` - }{} - - err := json.Unmarshal(value, &v) - if err != nil { - return err - } - a.Act = v.Act - a.KdfParams = v.KdfParams - a.Publisher = v.Publisher - a.Salt, err = hex.DecodeString(v.Salt) - if err != nil { - return err - } - if len(a.Salt) != 32 { - return errors.New("salt should be 32 bytes long") - } - a.Type = v.Type - return nil -} - -type KdfParams struct { - N int `json:"n"` - P int `json:"p"` - R int `json:"r"` -} - -type AccessType string - -const AccessTypePass = AccessType("pass") -const AccessTypePK = AccessType("pk") -const AccessTypeACT = AccessType("act") - -// NewAccessEntryPassword creates a manifest AccessEntry in order to create an ACT protected by a password -func NewAccessEntryPassword(salt []byte, kdfParams *KdfParams) (*AccessEntry, error) { - if len(salt) != 32 { - return nil, fmt.Errorf("salt should be 32 bytes long") - } - return &AccessEntry{ - Type: AccessTypePass, - Salt: salt, - KdfParams: kdfParams, - }, nil -} - -// NewAccessEntryPK creates a manifest AccessEntry in order to create an ACT protected by a pair of Elliptic Curve keys -func NewAccessEntryPK(publisher string, salt []byte) (*AccessEntry, error) { - if len(publisher) != 66 { - return nil, fmt.Errorf("publisher should be 66 characters long, got %d", len(publisher)) - } - if len(salt) != 32 { - return nil, fmt.Errorf("salt should be 32 bytes long") - } - return &AccessEntry{ - Type: AccessTypePK, - Publisher: publisher, - Salt: salt, - }, nil -} - -// NewAccessEntryACT creates a manifest AccessEntry in order to create an ACT protected by a combination of EC keys and passwords -func NewAccessEntryACT(publisher string, salt []byte, act string) (*AccessEntry, error) { - if len(salt) != 32 { - return nil, fmt.Errorf("salt should be 32 bytes long") - } - if len(publisher) != 66 { - return nil, fmt.Errorf("publisher should be 66 characters long") - } - - return &AccessEntry{ - Type: AccessTypeACT, - Publisher: publisher, - Salt: salt, - Act: act, - KdfParams: DefaultKdfParams, - }, nil -} - -// NOOPDecrypt is a generic decrypt function that is passed into the API in places where real ACT decryption capabilities are -// either unwanted, or alternatively, cannot be implemented in the immediate scope -func NOOPDecrypt(*ManifestEntry) error { - return nil -} - -var DefaultKdfParams = NewKdfParams(262144, 1, 8) - -// NewKdfParams returns a KdfParams struct with the given scrypt params -func NewKdfParams(n, p, r int) *KdfParams { - - return &KdfParams{ - N: n, - P: p, - R: r, - } -} - -// NewSessionKeyPassword creates a session key based on a shared secret (password) and the given salt -// and kdf parameters in the access entry -func NewSessionKeyPassword(password string, accessEntry *AccessEntry) ([]byte, error) { - if accessEntry.Type != AccessTypePass && accessEntry.Type != AccessTypeACT { - return nil, errors.New("incorrect access entry type") - - } - return sessionKeyPassword(password, accessEntry.Salt, accessEntry.KdfParams) -} - -func sessionKeyPassword(password string, salt []byte, kdfParams *KdfParams) ([]byte, error) { - return scrypt.Key( - []byte(password), - salt, - kdfParams.N, - kdfParams.R, - kdfParams.P, - 32, - ) -} - -// NewSessionKeyPK creates a new ACT Session Key using an ECDH shared secret for the given key pair and the given salt value -func NewSessionKeyPK(private *ecdsa.PrivateKey, public *ecdsa.PublicKey, salt []byte) ([]byte, error) { - granteePubEcies := ecies.ImportECDSAPublic(public) - privateKey := ecies.ImportECDSA(private) - - bytes, err := privateKey.GenerateShared(granteePubEcies, 16, 16) - if err != nil { - return nil, err - } - bytes = append(salt, bytes...) - sessionKey := crypto.Keccak256(bytes) - return sessionKey, nil -} - -func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.PrivateKey) DecryptFunc { - return func(m *ManifestEntry) error { - if m.Access == nil { - return nil - } - - allowed := false - requestDomain := sctx.GetHost(ctx) - for _, v := range AllowedDecryptDomains { - if strings.Contains(requestDomain, v) { - allowed = true - } - } - - if !allowed { - return ErrDecryptDomainForbidden - } - - switch m.Access.Type { - case "pass": - if credentials != "" { - key, err := NewSessionKeyPassword(credentials, m.Access) - if err != nil { - return err - } - - ref, err := hex.DecodeString(m.Hash) - if err != nil { - return err - } - - enc := NewRefEncryption(len(ref) - 8) - decodedRef, err := enc.Decrypt(ref, key) - if err != nil { - return ErrDecrypt - } - - m.Hash = hex.EncodeToString(decodedRef) - m.Access = nil - return nil - } - return ErrDecrypt - case "pk": - publisherBytes, err := hex.DecodeString(m.Access.Publisher) - if err != nil { - return ErrDecrypt - } - publisher, err := crypto.DecompressPubkey(publisherBytes) - if err != nil { - return ErrDecrypt - } - key, err := NewSessionKeyPK(pk, publisher, m.Access.Salt) - if err != nil { - return ErrDecrypt - } - ref, err := hex.DecodeString(m.Hash) - if err != nil { - return err - } - - enc := NewRefEncryption(len(ref) - 8) - decodedRef, err := enc.Decrypt(ref, key) - if err != nil { - return ErrDecrypt - } - - m.Hash = hex.EncodeToString(decodedRef) - m.Access = nil - return nil - case "act": - var ( - sessionKey []byte - err error - ) - - publisherBytes, err := hex.DecodeString(m.Access.Publisher) - if err != nil { - return ErrDecrypt - } - publisher, err := crypto.DecompressPubkey(publisherBytes) - if err != nil { - return ErrDecrypt - } - - sessionKey, err = NewSessionKeyPK(pk, publisher, m.Access.Salt) - if err != nil { - return ErrDecrypt - } - - found, ciphertext, decryptionKey, err := a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) - if err != nil { - return err - } - if !found { - // try to fall back to password - if credentials != "" { - sessionKey, err = NewSessionKeyPassword(credentials, m.Access) - if err != nil { - return err - } - found, ciphertext, decryptionKey, err = a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) - if err != nil { - return err - } - if !found { - return ErrDecrypt - } - } else { - return ErrDecrypt - } - } - enc := NewRefEncryption(len(ciphertext) - 8) - decodedRef, err := enc.Decrypt(ciphertext, decryptionKey) - if err != nil { - return ErrDecrypt - } - - ref, err := hex.DecodeString(m.Hash) - if err != nil { - return err - } - - enc = NewRefEncryption(len(ref) - 8) - decodedMainRef, err := enc.Decrypt(ref, decodedRef) - if err != nil { - return ErrDecrypt - } - m.Hash = hex.EncodeToString(decodedMainRef) - m.Access = nil - return nil - } - return ErrUnknownAccessType - } -} - -func (a *API) getACTDecryptionKey(ctx context.Context, actManifestAddress storage.Address, sessionKey []byte) (found bool, ciphertext, decryptionKey []byte, err error) { - hasher := sha3.NewLegacyKeccak256() - hasher.Write(append(sessionKey, 0)) - lookupKey := hasher.Sum(nil) - hasher.Reset() - - hasher.Write(append(sessionKey, 1)) - accessKeyDecryptionKey := hasher.Sum(nil) - hasher.Reset() - - lk := hex.EncodeToString(lookupKey) - list, err := a.GetManifestList(ctx, NOOPDecrypt, actManifestAddress, lk) - if err != nil { - return false, nil, nil, err - } - for _, v := range list.Entries { - if v.Path == lk { - cipherTextBytes, err := hex.DecodeString(v.Hash) - if err != nil { - return false, nil, nil, err - } - return true, cipherTextBytes, accessKeyDecryptionKey, nil - } - } - return false, nil, nil, nil -} - -func GenerateAccessControlManifest(ctx *cli.Context, ref string, accessKey []byte, ae *AccessEntry) (*Manifest, error) { - refBytes, err := hex.DecodeString(ref) - if err != nil { - return nil, err - } - // encrypt ref with accessKey - enc := NewRefEncryption(len(refBytes)) - encrypted, err := enc.Encrypt(refBytes, accessKey) - if err != nil { - return nil, err - } - - m := &Manifest{ - Entries: []ManifestEntry{ - { - Hash: hex.EncodeToString(encrypted), - ContentType: ManifestType, - ModTime: time.Now(), - Access: ae, - }, - }, - } - - return m, nil -} - -// DoPK is a helper function to the CLI API that handles the entire business logic for -// creating a session key and access entry given the cli context, ec keys and salt -func DoPK(ctx *cli.Context, privateKey *ecdsa.PrivateKey, granteePublicKey string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { - if granteePublicKey == "" { - return nil, nil, errors.New("need a grantee Public Key") - } - b, err := hex.DecodeString(granteePublicKey) - if err != nil { - log.Error("error decoding grantee public key", "err", err) - return nil, nil, err - } - - granteePub, err := crypto.DecompressPubkey(b) - if err != nil { - log.Error("error decompressing grantee public key", "err", err) - return nil, nil, err - } - - sessionKey, err = NewSessionKeyPK(privateKey, granteePub, salt) - if err != nil { - log.Error("error getting session key", "err", err) - return nil, nil, err - } - - ae, err = NewAccessEntryPK(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt) - if err != nil { - log.Error("error generating access entry", "err", err) - return nil, nil, err - } - - return sessionKey, ae, nil -} - -// DoACT is a helper function to the CLI API that handles the entire business logic for -// creating a access key, access entry and ACT manifest (including uploading it) given the cli context, ec keys, password grantees and salt -func DoACT(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grantees []string, encryptPasswords []string) (accessKey []byte, ae *AccessEntry, actManifest *Manifest, err error) { - if len(grantees) == 0 && len(encryptPasswords) == 0 { - return nil, nil, nil, errors.New("did not get any grantee public keys or any encryption passwords") - } - - publisherPub := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)) - grantees = append(grantees, publisherPub) - - accessKey = make([]byte, 32) - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - panic("reading from crypto/rand failed: " + err.Error()) - } - if _, err := io.ReadFull(rand.Reader, accessKey); err != nil { - panic("reading from crypto/rand failed: " + err.Error()) - } - - lookupPathEncryptedAccessKeyMap := make(map[string]string) - i := 0 - for _, v := range grantees { - i++ - if v == "" { - return nil, nil, nil, errors.New("need a grantee Public Key") - } - b, err := hex.DecodeString(v) - if err != nil { - log.Error("error decoding grantee public key", "err", err) - return nil, nil, nil, err - } - - granteePub, err := crypto.DecompressPubkey(b) - if err != nil { - log.Error("error decompressing grantee public key", "err", err) - return nil, nil, nil, err - } - sessionKey, err := NewSessionKeyPK(privateKey, granteePub, salt) - if err != nil { - return nil, nil, nil, err - } - - hasher := sha3.NewLegacyKeccak256() - hasher.Write(append(sessionKey, 0)) - lookupKey := hasher.Sum(nil) - - hasher.Reset() - hasher.Write(append(sessionKey, 1)) - - accessKeyEncryptionKey := hasher.Sum(nil) - - enc := NewRefEncryption(len(accessKey)) - encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) - if err != nil { - return nil, nil, nil, err - } - lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) - } - - for _, pass := range encryptPasswords { - sessionKey, err := sessionKeyPassword(pass, salt, DefaultKdfParams) - if err != nil { - return nil, nil, nil, err - } - hasher := sha3.NewLegacyKeccak256() - hasher.Write(append(sessionKey, 0)) - lookupKey := hasher.Sum(nil) - - hasher.Reset() - hasher.Write(append(sessionKey, 1)) - - accessKeyEncryptionKey := hasher.Sum(nil) - - enc := NewRefEncryption(len(accessKey)) - encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) - if err != nil { - return nil, nil, nil, err - } - lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) - } - - m := &Manifest{ - Entries: []ManifestEntry{}, - } - - for k, v := range lookupPathEncryptedAccessKeyMap { - m.Entries = append(m.Entries, ManifestEntry{ - Path: k, - Hash: v, - ContentType: "text/plain", - }) - } - - ae, err = NewAccessEntryACT(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt, "") - if err != nil { - return nil, nil, nil, err - } - - return accessKey, ae, m, nil -} - -// DoPassword is a helper function to the CLI API that handles the entire business logic for -// creating a session key and an access entry given the cli context, password and salt. -// By default - DefaultKdfParams are used as the scrypt params -func DoPassword(ctx *cli.Context, password string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { - ae, err = NewAccessEntryPassword(salt, DefaultKdfParams) - if err != nil { - return nil, nil, err - } - - sessionKey, err = NewSessionKeyPassword(password, ae) - if err != nil { - return nil, nil, err - } - return sessionKey, ae, nil -} diff --git a/swarm/api/api.go b/swarm/api/api.go deleted file mode 100644 index 96fb86e1c..000000000 --- a/swarm/api/api.go +++ /dev/null @@ -1,993 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -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" - "crypto/ecdsa" - "encoding/hex" - "errors" - "fmt" - "io" - "math/big" - "net/http" - "path" - "strings" - - "bytes" - "mime" - "path/filepath" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/contracts/ens" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/spancontext" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/ethereum/go-ethereum/swarm/storage/feed/lookup" - - opentracing "github.com/opentracing/opentracing-go" -) - -var ( - apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil) - apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil) - apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil) - apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil) - apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil) - apiManifestUpdateCount = metrics.NewRegisteredCounter("api.manifestupdate.count", nil) - apiManifestUpdateFail = metrics.NewRegisteredCounter("api.manifestupdate.fail", nil) - apiManifestListCount = metrics.NewRegisteredCounter("api.manifestlist.count", nil) - apiManifestListFail = metrics.NewRegisteredCounter("api.manifestlist.fail", nil) - apiDeleteCount = metrics.NewRegisteredCounter("api.delete.count", nil) - apiDeleteFail = metrics.NewRegisteredCounter("api.delete.fail", nil) - apiGetTarCount = metrics.NewRegisteredCounter("api.gettar.count", nil) - apiGetTarFail = metrics.NewRegisteredCounter("api.gettar.fail", nil) - apiUploadTarCount = metrics.NewRegisteredCounter("api.uploadtar.count", nil) - apiUploadTarFail = metrics.NewRegisteredCounter("api.uploadtar.fail", nil) - apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil) - apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil) - apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil) - apiAddFileFail = metrics.NewRegisteredCounter("api.addfile.fail", nil) - apiRmFileCount = metrics.NewRegisteredCounter("api.removefile.count", nil) - apiRmFileFail = metrics.NewRegisteredCounter("api.removefile.fail", nil) - apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil) - apiAppendFileFail = metrics.NewRegisteredCounter("api.appendfile.fail", nil) - apiGetInvalid = metrics.NewRegisteredCounter("api.get.invalid", nil) -) - -// Resolver interface resolve a domain name to a hash using ENS -type Resolver interface { - Resolve(string) (common.Hash, error) -} - -// ResolveValidator is used to validate the contained Resolver -type ResolveValidator interface { - Resolver - Owner(node [32]byte) (common.Address, error) - HeaderByNumber(context.Context, *big.Int) (*types.Header, error) -} - -// NoResolverError is returned by MultiResolver.Resolve if no resolver -// can be found for the address. -type NoResolverError struct { - TLD string -} - -// NewNoResolverError creates a NoResolverError for the given top level domain -func NewNoResolverError(tld string) *NoResolverError { - return &NoResolverError{TLD: tld} -} - -// Error NoResolverError implements error -func (e *NoResolverError) Error() string { - if e.TLD == "" { - return "no ENS resolver" - } - return fmt.Sprintf("no ENS endpoint configured to resolve .%s TLD names", e.TLD) -} - -// MultiResolver is used to resolve URL addresses based on their TLDs. -// Each TLD can have multiple resolvers, and the resolution from the -// first one in the sequence will be returned. -type MultiResolver struct { - resolvers map[string][]ResolveValidator - nameHash func(string) common.Hash -} - -// MultiResolverOption sets options for MultiResolver and is used as -// arguments for its constructor. -type MultiResolverOption func(*MultiResolver) - -// MultiResolverOptionWithResolver adds a Resolver to a list of resolvers -// for a specific TLD. If TLD is an empty string, the resolver will be added -// to the list of default resolver, the ones that will be used for resolution -// of addresses which do not have their TLD resolver specified. -func MultiResolverOptionWithResolver(r ResolveValidator, tld string) MultiResolverOption { - return func(m *MultiResolver) { - m.resolvers[tld] = append(m.resolvers[tld], r) - } -} - -// NewMultiResolver creates a new instance of MultiResolver. -func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) { - m = &MultiResolver{ - resolvers: make(map[string][]ResolveValidator), - nameHash: ens.EnsNode, - } - for _, o := range opts { - o(m) - } - return m -} - -// Resolve resolves address by choosing a Resolver by TLD. -// If there are more default Resolvers, or for a specific TLD, -// the Hash from the first one which does not return error -// will be returned. -func (m *MultiResolver) Resolve(addr string) (h common.Hash, err error) { - rs, err := m.getResolveValidator(addr) - if err != nil { - return h, err - } - for _, r := range rs { - h, err = r.Resolve(addr) - if err == nil { - return - } - } - return -} - -// getResolveValidator uses the hostname to retrieve the resolver associated with the top level domain -func (m *MultiResolver) getResolveValidator(name string) ([]ResolveValidator, error) { - rs := m.resolvers[""] - tld := path.Ext(name) - if tld != "" { - tld = tld[1:] - rstld, ok := m.resolvers[tld] - if ok { - return rstld, nil - } - } - if len(rs) == 0 { - return rs, NewNoResolverError(tld) - } - return rs, nil -} - -/* -API implements webserver/file system related content storage and retrieval -on top of the FileStore -it is the public interface of the FileStore which is included in the ethereum stack -*/ -type API struct { - feed *feed.Handler - fileStore *storage.FileStore - dns Resolver - Tags *chunk.Tags - Decryptor func(context.Context, string) DecryptFunc -} - -// NewAPI the api constructor initialises a new API instance. -func NewAPI(fileStore *storage.FileStore, dns Resolver, feedHandler *feed.Handler, pk *ecdsa.PrivateKey, tags *chunk.Tags) (self *API) { - self = &API{ - fileStore: fileStore, - dns: dns, - feed: feedHandler, - Tags: tags, - Decryptor: func(ctx context.Context, credentials string) DecryptFunc { - return self.doDecrypt(ctx, credentials, pk) - }, - } - return -} - -// Retrieve FileStore reader API -func (a *API) Retrieve(ctx context.Context, addr storage.Address) (reader storage.LazySectionReader, isEncrypted bool) { - return a.fileStore.Retrieve(ctx, addr) -} - -// Store wraps the Store API call of the embedded FileStore -func (a *API) Store(ctx context.Context, data io.Reader, size int64, toEncrypt bool) (addr storage.Address, wait func(ctx context.Context) error, err error) { - log.Debug("api.store", "size", size) - return a.fileStore.Store(ctx, data, size, toEncrypt) -} - -// Resolve a name into a content-addressed hash -// where address could be an ENS name, or a content addressed hash -func (a *API) Resolve(ctx context.Context, address string) (storage.Address, error) { - // if DNS is not configured, return an error - if a.dns == nil { - if hashMatcher.MatchString(address) { - return common.Hex2Bytes(address), nil - } - apiResolveFail.Inc(1) - return nil, fmt.Errorf("no DNS to resolve name: %q", address) - } - // try and resolve the address - resolved, err := a.dns.Resolve(address) - if err != nil { - if hashMatcher.MatchString(address) { - return common.Hex2Bytes(address), nil - } - return nil, err - } - return resolved[:], nil -} - -// Resolve resolves a URI to an Address using the MultiResolver. -func (a *API) ResolveURI(ctx context.Context, uri *URI, credentials string) (storage.Address, error) { - apiResolveCount.Inc(1) - log.Trace("resolving", "uri", uri.Addr) - - var sp opentracing.Span - ctx, sp = spancontext.StartSpan( - ctx, - "api.resolve") - defer sp.Finish() - - // if the URI is immutable, check if the address looks like a hash - if uri.Immutable() { - key := uri.Address() - if key == nil { - return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr) - } - return key, nil - } - - addr, err := a.Resolve(ctx, uri.Addr) - if err != nil { - return nil, err - } - - if uri.Path == "" { - return addr, nil - } - walker, err := a.NewManifestWalker(ctx, addr, a.Decryptor(ctx, credentials), nil) - if err != nil { - return nil, err - } - var entry *ManifestEntry - walker.Walk(func(e *ManifestEntry) error { - // if the entry matches the path, set entry and stop - // the walk - if e.Path == uri.Path { - entry = e - // return an error to cancel the walk - return errors.New("found") - } - // ignore non-manifest files - if e.ContentType != ManifestType { - return nil - } - // if the manifest's path is a prefix of the - // requested path, recurse into it by returning - // nil and continuing the walk - if strings.HasPrefix(uri.Path, e.Path) { - return nil - } - return ErrSkipManifest - }) - if entry == nil { - return nil, errors.New("not found") - } - addr = storage.Address(common.Hex2Bytes(entry.Hash)) - return addr, nil -} - -// Get uses iterative manifest retrieval and prefix matching -// to resolve basePath to content using FileStore retrieve -// it returns a section reader, mimeType, status, the key of the actual content and an error -func (a *API) Get(ctx context.Context, decrypt DecryptFunc, manifestAddr storage.Address, path string) (reader storage.LazySectionReader, mimeType string, status int, contentAddr storage.Address, err error) { - log.Debug("api.get", "key", manifestAddr, "path", path) - apiGetCount.Inc(1) - trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - return nil, "", http.StatusNotFound, nil, err - } - - log.Debug("trie getting entry", "key", manifestAddr, "path", path) - entry, _ := trie.getEntry(path) - - if entry != nil { - log.Debug("trie got entry", "key", manifestAddr, "path", path, "entry.Hash", entry.Hash) - - if entry.ContentType == ManifestType { - log.Debug("entry is manifest", "key", manifestAddr, "new key", entry.Hash) - adr, err := hex.DecodeString(entry.Hash) - if err != nil { - return nil, "", 0, nil, err - } - return a.Get(ctx, decrypt, adr, entry.Path) - } - - // we need to do some extra work if this is a Swarm feed manifest - if entry.ContentType == FeedContentType { - if entry.Feed == nil { - return reader, mimeType, status, nil, fmt.Errorf("Cannot decode Feed in manifest") - } - _, err := a.feed.Lookup(ctx, feed.NewQueryLatest(entry.Feed, lookup.NoClue)) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - log.Debug(fmt.Sprintf("get feed update content error: %v", err)) - return reader, mimeType, status, nil, err - } - // get the data of the update - _, contentAddr, err := a.feed.GetContent(entry.Feed) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - log.Warn(fmt.Sprintf("get feed update content error: %v", err)) - return reader, mimeType, status, nil, err - } - - // extract content hash - if len(contentAddr) != storage.AddressLength { - apiGetInvalid.Inc(1) - status = http.StatusUnprocessableEntity - errorMessage := fmt.Sprintf("invalid swarm hash in feed update. Expected %d bytes. Got %d", storage.AddressLength, len(contentAddr)) - log.Warn(errorMessage) - return reader, mimeType, status, nil, errors.New(errorMessage) - } - manifestAddr = storage.Address(contentAddr) - log.Trace("feed update contains swarm hash", "key", manifestAddr) - - // get the manifest the swarm hash points to - trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, NOOPDecrypt) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - log.Warn(fmt.Sprintf("loadManifestTrie (feed update) 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 (feed update) entry for '%s' not found", path) - log.Trace("manifest (feed update) entry not found", "key", manifestAddr, "path", path) - return reader, mimeType, status, nil, err - } - } - - // regardless of feed update manifests or normal manifests we will converge at this point - // get the key the manifest entry points to and serve it if it's unambiguous - contentAddr = common.Hex2Bytes(entry.Hash) - status = entry.Status - if status == http.StatusMultipleChoices { - apiGetHTTP300.Inc(1) - return nil, entry.ContentType, status, contentAddr, err - } - mimeType = entry.ContentType - log.Debug("content lookup key", "key", contentAddr, "mimetype", mimeType) - reader, _ = a.fileStore.Retrieve(ctx, contentAddr) - } else { - // no entry found - status = http.StatusNotFound - apiGetNotFound.Inc(1) - err = fmt.Errorf("Not found: could not find resource '%s'", path) - log.Trace("manifest entry not found", "key", contentAddr, "path", path) - } - return -} - -func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Address, error) { - apiDeleteCount.Inc(1) - uri, err := Parse("bzz:/" + addr) - if err != nil { - apiDeleteFail.Inc(1) - return nil, err - } - key, err := a.ResolveURI(ctx, uri, EmptyCredentials) - - if err != nil { - return nil, err - } - newKey, err := a.UpdateManifest(ctx, key, func(mw *ManifestWriter) error { - log.Debug(fmt.Sprintf("removing %s from manifest %s", path, key.Log())) - return mw.RemoveEntry(path) - }) - if err != nil { - apiDeleteFail.Inc(1) - return nil, err - } - - return newKey, nil -} - -// GetDirectoryTar fetches a requested directory as a tarstream -// it returns an io.Reader and an error. Do not forget to Close() the returned ReadCloser -func (a *API) GetDirectoryTar(ctx context.Context, decrypt DecryptFunc, uri *URI) (io.ReadCloser, error) { - apiGetTarCount.Inc(1) - addr, err := a.Resolve(ctx, uri.Addr) - if err != nil { - return nil, err - } - walker, err := a.NewManifestWalker(ctx, addr, decrypt, nil) - if err != nil { - apiGetTarFail.Inc(1) - return nil, err - } - - piper, pipew := io.Pipe() - - tw := tar.NewWriter(pipew) - - go func() { - err := walker.Walk(func(entry *ManifestEntry) error { - // ignore manifests (walk will recurse into them) - if entry.ContentType == ManifestType { - return nil - } - - // retrieve the entry's key and size - reader, _ := a.Retrieve(ctx, storage.Address(common.Hex2Bytes(entry.Hash))) - size, err := reader.Size(ctx, nil) - if err != nil { - return err - } - - // write a tar header for the entry - hdr := &tar.Header{ - Name: entry.Path, - Mode: entry.Mode, - Size: size, - ModTime: entry.ModTime, - Xattrs: map[string]string{ - "user.swarm.content-type": entry.ContentType, - }, - } - - if err := tw.WriteHeader(hdr); err != nil { - return err - } - - // copy the file into the tar stream - n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) - if err != nil { - return err - } else if n != size { - return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) - } - - return nil - }) - // close tar writer before closing pipew - // to flush remaining data to pipew - // regardless of error value - tw.Close() - if err != nil { - apiGetTarFail.Inc(1) - pipew.CloseWithError(err) - } else { - pipew.Close() - } - }() - - return piper, nil -} - -// GetManifestList lists the manifest entries for the specified address and prefix -// and returns it as a ManifestList -func (a *API) GetManifestList(ctx context.Context, decryptor DecryptFunc, addr storage.Address, prefix string) (list ManifestList, err error) { - apiManifestListCount.Inc(1) - walker, err := a.NewManifestWalker(ctx, addr, decryptor, nil) - if err != nil { - apiManifestListFail.Inc(1) - return ManifestList{}, err - } - - err = walker.Walk(func(entry *ManifestEntry) error { - // handle non-manifest files - if entry.ContentType != ManifestType { - // ignore the file if it doesn't have the specified prefix - if !strings.HasPrefix(entry.Path, prefix) { - return nil - } - - // if the path after the prefix contains a slash, add a - // common prefix to the list, otherwise add the entry - suffix := strings.TrimPrefix(entry.Path, prefix) - if index := strings.Index(suffix, "/"); index > -1 { - list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) - return nil - } - if entry.Path == "" { - entry.Path = "/" - } - list.Entries = append(list.Entries, entry) - return nil - } - - // if the manifest's path is a prefix of the specified prefix - // then just recurse into the manifest by returning nil and - // continuing the walk - if strings.HasPrefix(prefix, entry.Path) { - return nil - } - - // if the manifest's path has the specified prefix, then if the - // path after the prefix contains a slash, add a common prefix - // to the list and skip the manifest, otherwise recurse into - // the manifest by returning nil and continuing the walk - if strings.HasPrefix(entry.Path, prefix) { - suffix := strings.TrimPrefix(entry.Path, prefix) - if index := strings.Index(suffix, "/"); index > -1 { - list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) - return ErrSkipManifest - } - return nil - } - - // the manifest neither has the prefix or needs recursing in to - // so just skip it - return ErrSkipManifest - }) - - if err != nil { - apiManifestListFail.Inc(1) - return ManifestList{}, err - } - - return list, nil -} - -func (a *API) UpdateManifest(ctx context.Context, addr storage.Address, update func(mw *ManifestWriter) error) (storage.Address, error) { - apiManifestUpdateCount.Inc(1) - mw, err := a.NewManifestWriter(ctx, addr, nil) - if err != nil { - apiManifestUpdateFail.Inc(1) - return nil, err - } - - if err := update(mw); err != nil { - apiManifestUpdateFail.Inc(1) - return nil, err - } - - addr, err = mw.Store() - if err != nil { - apiManifestUpdateFail.Inc(1) - return nil, err - } - log.Debug(fmt.Sprintf("generated manifest %s", addr)) - return addr, nil -} - -// Modify loads manifest and checks the content hash before recalculating and storing the manifest. -func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) { - apiModifyCount.Inc(1) - quitC := make(chan bool) - trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - apiModifyFail.Inc(1) - return nil, err - } - if contentHash != "" { - entry := newManifestTrieEntry(&ManifestEntry{ - Path: path, - ContentType: contentType, - }, nil) - entry.Hash = contentHash - trie.addEntry(entry, quitC) - } else { - trie.deleteEntry(path, quitC) - } - - if err := trie.recalcAndStore(); err != nil { - apiModifyFail.Inc(1) - return nil, err - } - return trie.ref, nil -} - -// AddFile creates a new manifest entry, adds it to swarm, then adds a file to swarm. -func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []byte, nameresolver bool) (storage.Address, string, error) { - apiAddFileCount.Inc(1) - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - mkey, err := a.ResolveURI(ctx, uri, EmptyCredentials) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - - // trim the root dir we added - if path[:1] == "/" { - path = path[1:] - } - - entry := &ManifestEntry{ - Path: filepath.Join(path, fname), - ContentType: mime.TypeByExtension(filepath.Ext(fname)), - Mode: 0700, - Size: int64(len(content)), - ModTime: time.Now(), - } - - mw, err := a.NewManifestWriter(ctx, mkey, nil) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - - fkey, err := mw.AddEntry(ctx, bytes.NewReader(content), entry) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - - newMkey, err := mw.Store() - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - - } - - return fkey, newMkey.String(), nil -} - -func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestPath, defaultPath string, mw *ManifestWriter) (storage.Address, error) { - apiUploadTarCount.Inc(1) - var contentKey storage.Address - tr := tar.NewReader(bodyReader) - defer bodyReader.Close() - var defaultPathFound bool - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - apiUploadTarFail.Inc(1) - return nil, fmt.Errorf("error reading tar stream: %s", err) - } - - // only store regular files - if !hdr.FileInfo().Mode().IsRegular() { - continue - } - - // 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: contentType, - Mode: hdr.Mode, - Size: hdr.Size, - ModTime: hdr.ModTime, - } - contentKey, err = mw.AddEntry(ctx, tr, entry) - if err != nil { - apiUploadTarFail.Inc(1) - 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: contentType, - Mode: hdr.Mode, - Size: hdr.Size, - ModTime: hdr.ModTime, - } - contentKey, err = mw.AddEntry(ctx, nil, entry) - if err != nil { - apiUploadTarFail.Inc(1) - return nil, fmt.Errorf("error adding default manifest entry from tar stream: %s", err) - } - defaultPathFound = true - } - } - if defaultPath != "" && !defaultPathFound { - return contentKey, fmt.Errorf("default path %q not found", defaultPath) - } - return contentKey, nil -} - -// RemoveFile removes a file entry in a manifest. -func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname string, nameresolver bool) (string, error) { - apiRmFileCount.Inc(1) - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - mkey, err := a.ResolveURI(ctx, uri, EmptyCredentials) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - - // trim the root dir we added - if path[:1] == "/" { - path = path[1:] - } - - mw, err := a.NewManifestWriter(ctx, mkey, nil) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - - err = mw.RemoveEntry(filepath.Join(path, fname)) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - - newMkey, err := mw.Store() - if err != nil { - apiRmFileFail.Inc(1) - return "", err - - } - - return newMkey.String(), nil -} - -// AppendFile removes old manifest, appends file entry to new manifest and adds it to Swarm. -func (a *API) AppendFile(ctx context.Context, mhash, path, fname string, existingSize int64, content []byte, oldAddr storage.Address, offset int64, addSize int64, nameresolver bool) (storage.Address, string, error) { - apiAppendFileCount.Inc(1) - - buffSize := offset + addSize - if buffSize < existingSize { - buffSize = existingSize - } - - buf := make([]byte, buffSize) - - oldReader, _ := a.Retrieve(ctx, oldAddr) - io.ReadAtLeast(oldReader, buf, int(offset)) - - newReader := bytes.NewReader(content) - io.ReadAtLeast(newReader, buf[offset:], int(addSize)) - - if buffSize < existingSize { - io.ReadAtLeast(oldReader, buf[addSize:], int(buffSize)) - } - - combinedReader := bytes.NewReader(buf) - totalSize := int64(len(buf)) - - // TODO(jmozah): to append using pyramid chunker when it is ready - //oldReader := a.Retrieve(oldKey) - //newReader := bytes.NewReader(content) - //combinedReader := io.MultiReader(oldReader, newReader) - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - mkey, err := a.ResolveURI(ctx, uri, EmptyCredentials) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - // trim the root dir we added - if path[:1] == "/" { - path = path[1:] - } - - mw, err := a.NewManifestWriter(ctx, mkey, nil) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - err = mw.RemoveEntry(filepath.Join(path, fname)) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - entry := &ManifestEntry{ - Path: filepath.Join(path, fname), - ContentType: mime.TypeByExtension(filepath.Ext(fname)), - Mode: 0700, - Size: totalSize, - ModTime: time.Now(), - } - - fkey, err := mw.AddEntry(ctx, io.Reader(combinedReader), entry) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - newMkey, err := mw.Store() - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - - } - - return fkey, newMkey.String(), nil -} - -// BuildDirectoryTree used by swarmfs_unix -func (a *API) BuildDirectoryTree(ctx context.Context, mhash string, nameresolver bool) (addr storage.Address, manifestEntryMap map[string]*manifestTrieEntry, err error) { - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - return nil, nil, err - } - addr, err = a.Resolve(ctx, uri.Addr) - if err != nil { - return nil, nil, err - } - - quitC := make(chan bool) - rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - return nil, nil, fmt.Errorf("can't load manifest %v: %v", addr.String(), err) - } - - manifestEntryMap = map[string]*manifestTrieEntry{} - err = rootTrie.listWithPrefix(uri.Path, quitC, func(entry *manifestTrieEntry, suffix string) { - manifestEntryMap[suffix] = entry - }) - - if err != nil { - return nil, nil, fmt.Errorf("list with prefix failed %v: %v", addr.String(), err) - } - return addr, manifestEntryMap, nil -} - -// FeedsLookup finds Swarm feeds updates at specific points in time, or the latest update -func (a *API) FeedsLookup(ctx context.Context, query *feed.Query) ([]byte, error) { - _, err := a.feed.Lookup(ctx, query) - if err != nil { - return nil, err - } - var data []byte - _, data, err = a.feed.GetContent(&query.Feed) - if err != nil { - return nil, err - } - return data, nil -} - -// FeedsNewRequest creates a Request object to update a specific feed -func (a *API) FeedsNewRequest(ctx context.Context, feed *feed.Feed) (*feed.Request, error) { - return a.feed.NewRequest(ctx, feed) -} - -// FeedsUpdate publishes a new update on the given feed -func (a *API) FeedsUpdate(ctx context.Context, request *feed.Request) (storage.Address, error) { - return a.feed.Update(ctx, request) -} - -// ErrCannotLoadFeedManifest is returned when looking up a feeds manifest fails -var ErrCannotLoadFeedManifest = errors.New("Cannot load feed manifest") - -// ErrNotAFeedManifest is returned when the address provided returned something other than a valid manifest -var ErrNotAFeedManifest = errors.New("Not a feed manifest") - -// ResolveFeedManifest retrieves the Swarm feed manifest for the given address, and returns the referenced Feed. -func (a *API) ResolveFeedManifest(ctx context.Context, addr storage.Address) (*feed.Feed, error) { - trie, err := loadManifest(ctx, a.fileStore, addr, nil, NOOPDecrypt) - if err != nil { - return nil, ErrCannotLoadFeedManifest - } - - entry, _ := trie.getEntry("") - if entry.ContentType != FeedContentType { - return nil, ErrNotAFeedManifest - } - - return entry.Feed, nil -} - -// ErrCannotResolveFeedURI is returned when the ENS resolver is not able to translate a name to a Swarm feed -var ErrCannotResolveFeedURI = errors.New("Cannot resolve Feed URI") - -// ErrCannotResolveFeed is returned when values provided are not enough or invalid to recreate a -// feed out of them. -var ErrCannotResolveFeed = errors.New("Cannot resolve Feed") - -// ResolveFeed attempts to extract feed information out of the manifest, if provided -// If not, it attempts to extract the feed out of a set of key-value pairs -func (a *API) ResolveFeed(ctx context.Context, uri *URI, values feed.Values) (*feed.Feed, error) { - var fd *feed.Feed - 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, ErrCannotResolveFeedURI - } - } - - // get the Swarm feed from the manifest - fd, err = a.ResolveFeedManifest(ctx, manifestAddr) - if err != nil { - return nil, err - } - log.Debug("handle.get.feed: resolved", "manifestkey", manifestAddr, "feed", fd.Hex()) - } else { - var f feed.Feed - if err := f.FromValues(values); err != nil { - return nil, ErrCannotResolveFeed - - } - fd = &f - } - return fd, 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 ctype, nil -} diff --git a/swarm/api/api_test.go b/swarm/api/api_test.go deleted file mode 100644 index 4a5f92362..000000000 --- a/swarm/api/api_test.go +++ /dev/null @@ -1,576 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "bytes" - "context" - crand "crypto/rand" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "math/big" - "os" - "strings" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/sctx" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/testutil" -) - -func init() { - loglevel := flag.Int("loglevel", 2, "loglevel") - flag.Parse() - log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) -} - -func testAPI(t *testing.T, f func(*API, *chunk.Tags, bool)) { - for _, v := range []bool{true, false} { - datadir, err := ioutil.TempDir("", "bzz-test") - if err != nil { - t.Fatalf("unable to create temp dir: %v", err) - } - defer os.RemoveAll(datadir) - tags := chunk.NewTags() - fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32), tags) - if err != nil { - return - } - api := NewAPI(fileStore, nil, nil, nil, tags) - f(api, tags, v) - } -} - -type testResponse struct { - reader storage.LazySectionReader - *Response -} - -type Response struct { - MimeType string - Status int - Size int64 - Content string -} - -func checkResponse(t *testing.T, resp *testResponse, exp *Response) { - - if resp.MimeType != exp.MimeType { - t.Errorf("incorrect mimeType. expected '%s', got '%s'", exp.MimeType, resp.MimeType) - } - if resp.Status != exp.Status { - t.Errorf("incorrect status. expected '%d', got '%d'", exp.Status, resp.Status) - } - if resp.Size != exp.Size { - t.Errorf("incorrect size. expected '%d', got '%d'", exp.Size, resp.Size) - } - if resp.reader != nil { - content := make([]byte, resp.Size) - read, _ := resp.reader.Read(content) - if int64(read) != exp.Size { - t.Errorf("incorrect content length. expected '%d...', got '%d...'", read, exp.Size) - } - resp.Content = string(content) - } - if resp.Content != exp.Content { - // if !bytes.Equal(resp.Content, exp.Content) - t.Errorf("incorrect content. expected '%s...', got '%s...'", string(exp.Content), string(resp.Content)) - } -} - -// func expResponse(content []byte, mimeType string, status int) *Response { -func expResponse(content string, mimeType string, status int) *Response { - log.Trace(fmt.Sprintf("expected content (%v): %v ", len(content), content)) - return &Response{mimeType, status, int64(len(content)), content} -} - -func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse { - addr := storage.Address(common.Hex2Bytes(bzzhash)) - reader, mimeType, status, _, err := api.Get(context.TODO(), NOOPDecrypt, addr, path) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - quitC := make(chan bool) - size, err := reader.Size(context.TODO(), quitC) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - log.Trace(fmt.Sprintf("reader size: %v ", size)) - s := make([]byte, size) - _, err = reader.Read(s) - if err != io.EOF { - t.Fatalf("unexpected error: %v", err) - } - reader.Seek(0, 0) - return &testResponse{reader, &Response{mimeType, status, size, string(s)}} -} - -func TestApiPut(t *testing.T) { - testAPI(t, func(api *API, tags *chunk.Tags, toEncrypt bool) { - content := "hello" - exp := expResponse(content, "text/plain", 0) - ctx := context.TODO() - addr, wait, err := putString(ctx, api, content, exp.MimeType, toEncrypt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = wait(ctx) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - resp := testGet(t, api, addr.Hex(), "") - checkResponse(t, resp, exp) - tag := tags.All()[0] - testutil.CheckTag(t, tag, 2, 2, 0, 2) //1 chunk data, 1 chunk manifest - }) -} - -// TestApiTagLarge tests that the the number of chunks counted is larger for a larger input -func TestApiTagLarge(t *testing.T) { - const contentLength = 4096 * 4095 - testAPI(t, func(api *API, tags *chunk.Tags, toEncrypt bool) { - randomContentReader := io.LimitReader(crand.Reader, int64(contentLength)) - tag, err := api.Tags.New("unnamed-tag", 0) - if err != nil { - t.Fatal(err) - } - ctx := sctx.SetTag(context.Background(), tag.Uid) - key, waitContent, err := api.Store(ctx, randomContentReader, int64(contentLength), toEncrypt) - if err != nil { - t.Fatal(err) - } - err = waitContent(ctx) - if err != nil { - t.Fatal(err) - } - tag.DoneSplit(key) - - if toEncrypt { - tag := tags.All()[0] - expect := int64(4095 + 64 + 1) - testutil.CheckTag(t, tag, expect, expect, 0, expect) - } else { - tag := tags.All()[0] - expect := int64(4095 + 32 + 1) - testutil.CheckTag(t, tag, expect, expect, 0, expect) - } - }) -} - -// testResolver implements the Resolver interface and either returns the given -// hash if it is set, or returns a "name not found" error -type testResolveValidator struct { - hash *common.Hash -} - -func newTestResolveValidator(addr string) *testResolveValidator { - r := &testResolveValidator{} - if addr != "" { - hash := common.HexToHash(addr) - r.hash = &hash - } - return r -} - -func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) { - if t.hash == nil { - return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) - } - return *t.hash, nil -} - -func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) { - return -} -func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) { - return -} - -// TestAPIResolve tests resolving URIs which can either contain content hashes -// or ENS names -func TestAPIResolve(t *testing.T) { - ensAddr := "swarm.eth" - hashAddr := "1111111111111111111111111111111111111111111111111111111111111111" - resolvedAddr := "2222222222222222222222222222222222222222222222222222222222222222" - doesResolve := newTestResolveValidator(resolvedAddr) - doesntResolve := newTestResolveValidator("") - - type test struct { - desc string - dns Resolver - addr string - immutable bool - result string - expectErr error - } - - tests := []*test{ - { - desc: "DNS not configured, hash address, returns hash address", - dns: nil, - addr: hashAddr, - result: hashAddr, - }, - { - desc: "DNS not configured, ENS address, returns error", - dns: nil, - addr: ensAddr, - expectErr: errors.New(`no DNS to resolve name: "swarm.eth"`), - }, - { - desc: "DNS configured, hash address, hash resolves, returns resolved address", - dns: doesResolve, - addr: hashAddr, - result: resolvedAddr, - }, - { - desc: "DNS configured, immutable hash address, hash resolves, returns hash address", - dns: doesResolve, - addr: hashAddr, - immutable: true, - result: hashAddr, - }, - { - desc: "DNS configured, hash address, hash doesn't resolve, returns hash address", - dns: doesntResolve, - addr: hashAddr, - result: hashAddr, - }, - { - desc: "DNS configured, ENS address, name resolves, returns resolved address", - dns: doesResolve, - addr: ensAddr, - result: resolvedAddr, - }, - { - desc: "DNS configured, immutable ENS address, name resolves, returns error", - dns: doesResolve, - addr: ensAddr, - immutable: true, - expectErr: errors.New(`immutable address not a content hash: "swarm.eth"`), - }, - { - desc: "DNS configured, ENS address, name doesn't resolve, returns error", - dns: doesntResolve, - addr: ensAddr, - expectErr: errors.New(`DNS name not found: "swarm.eth"`), - }, - } - for _, x := range tests { - t.Run(x.desc, func(t *testing.T) { - api := &API{dns: x.dns} - uri := &URI{Addr: x.addr, Scheme: "bzz"} - if x.immutable { - uri.Scheme = "bzz-immutable" - } - res, err := api.ResolveURI(context.TODO(), uri, "") - if err == nil { - if x.expectErr != nil { - t.Fatalf("expected error %q, got result %q", x.expectErr, res) - } - if res.String() != x.result { - t.Fatalf("expected result %q, got %q", x.result, res) - } - } else { - if x.expectErr == nil { - t.Fatalf("expected no error, got %q", err) - } - if err.Error() != x.expectErr.Error() { - t.Fatalf("expected error %q, got %q", x.expectErr, err) - } - } - }) - } -} - -func TestMultiResolver(t *testing.T) { - doesntResolve := newTestResolveValidator("") - - ethAddr := "swarm.eth" - ethHash := "0x2222222222222222222222222222222222222222222222222222222222222222" - ethResolve := newTestResolveValidator(ethHash) - - testAddr := "swarm.test" - testHash := "0x1111111111111111111111111111111111111111111111111111111111111111" - testResolve := newTestResolveValidator(testHash) - - tests := []struct { - desc string - r Resolver - addr string - result string - err error - }{ - { - desc: "No resolvers, returns error", - r: NewMultiResolver(), - err: NewNoResolverError(""), - }, - { - desc: "One default resolver, returns resolved address", - r: NewMultiResolver(MultiResolverOptionWithResolver(ethResolve, "")), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Two default resolvers, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(ethResolve, ""), - MultiResolverOptionWithResolver(ethResolve, ""), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Two default resolvers, first doesn't resolve, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(ethResolve, ""), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Default resolver doesn't resolve, tld resolver resolve, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Three TLD resolvers, third resolves, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, "eth"), - MultiResolverOptionWithResolver(doesntResolve, "eth"), - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "One TLD resolver doesn't resolve, returns error", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "One defautl and one TLD resolver, all doesn't resolve, returns error", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(doesntResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - err: errors.New(`DNS name not found: "swarm.eth"`), - }, - { - desc: "Two TLD resolvers, both resolve, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(ethResolve, "eth"), - MultiResolverOptionWithResolver(testResolve, "test"), - ), - addr: testAddr, - result: testHash, - }, - { - desc: "One TLD resolver, no default resolver, returns error for different TLD", - r: NewMultiResolver( - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: testAddr, - err: NewNoResolverError("test"), - }, - } - for _, x := range tests { - t.Run(x.desc, func(t *testing.T) { - res, err := x.r.Resolve(x.addr) - if err == nil { - if x.err != nil { - t.Fatalf("expected error %q, got result %q", x.err, res.Hex()) - } - if res.Hex() != x.result { - t.Fatalf("expected result %q, got %q", x.result, res.Hex()) - } - } else { - if x.err == nil { - t.Fatalf("expected no error, got %q", err) - } - if err.Error() != x.err.Error() { - t.Fatalf("expected error %q, got %q", x.err, err) - } - } - }) - } -} - -func TestDecryptOriginForbidden(t *testing.T) { - ctx := context.TODO() - ctx = sctx.SetHost(ctx, "swarm-gateways.net") - - me := &ManifestEntry{ - Access: &AccessEntry{Type: AccessTypePass}, - } - - api := NewAPI(nil, nil, nil, nil, chunk.NewTags()) - - f := api.Decryptor(ctx, "") - err := f(me) - if err != ErrDecryptDomainForbidden { - t.Fatalf("should fail with ErrDecryptDomainForbidden, got %v", err) - } -} - -func TestDecryptOrigin(t *testing.T) { - for _, v := range []struct { - host string - expectError error - }{ - { - host: "localhost", - expectError: ErrDecrypt, - }, - { - host: "127.0.0.1", - expectError: ErrDecrypt, - }, - { - host: "swarm-gateways.net", - expectError: ErrDecryptDomainForbidden, - }, - } { - ctx := context.TODO() - ctx = sctx.SetHost(ctx, v.host) - - me := &ManifestEntry{ - Access: &AccessEntry{Type: AccessTypePass}, - } - - api := NewAPI(nil, nil, nil, nil, chunk.NewTags()) - - f := api.Decryptor(ctx, "") - err := f(me) - if err != v.expectError { - t.Fatalf("should fail with %v, got %v", v.expectError, err) - } - } -} - -func TestDetectContentType(t *testing.T) { - for _, tc := range []struct { - file string - content string - expectedContentType string - }{ - { - file: "file-with-correct-css.css", - content: "body {background-color: orange}", - expectedContentType: "text/css; charset=utf-8", - }, - { - file: "empty-file.css", - content: "", - expectedContentType: "text/css; charset=utf-8", - }, - { - file: "empty-file.pdf", - content: "", - expectedContentType: "application/pdf", - }, - { - file: "empty-file.md", - content: "", - expectedContentType: "text/markdown; charset=utf-8", - }, - { - file: "empty-file-with-unknown-content.strangeext", - content: "", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "file-with-unknown-extension-and-content.strangeext", - content: "Lorem Ipsum", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "file-no-extension", - content: "Lorem Ipsum", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "file-no-extension-no-content", - content: "", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "css-file-with-html-inside.css", - content: "<!doctype html><html><head></head><body></body></html>", - expectedContentType: "text/css; charset=utf-8", - }, - } { - t.Run(tc.file, func(t *testing.T) { - detected, err := DetectContentType(tc.file, bytes.NewReader([]byte(tc.content))) - if err != nil { - t.Fatal(err) - } - - if detected != tc.expectedContentType { - t.Fatalf("File: %s, Expected mime type %s, got %s", tc.file, tc.expectedContentType, detected) - } - - }) - } -} - -// putString provides singleton manifest creation on top of api.API -func putString(ctx context.Context, a *API, content string, contentType string, toEncrypt bool) (k storage.Address, wait func(context.Context) error, err error) { - r := strings.NewReader(content) - tag, err := a.Tags.New("unnamed-tag", 0) - - log.Trace("created new tag", "uid", tag.Uid) - - cCtx := sctx.SetTag(ctx, tag.Uid) - key, waitContent, err := a.Store(cCtx, r, int64(len(content)), toEncrypt) - if err != nil { - return nil, nil, err - } - manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType) - r = strings.NewReader(manifest) - key, waitManifest, err := a.Store(cCtx, r, int64(len(manifest)), toEncrypt) - if err != nil { - return nil, nil, err - } - tag.DoneSplit(key) - return key, func(ctx context.Context) error { - err := waitContent(ctx) - if err != nil { - return err - } - return waitManifest(ctx) - }, nil -} diff --git a/swarm/api/client/client.go b/swarm/api/client/client.go deleted file mode 100644 index 9ad0948f4..000000000 --- a/swarm/api/client/client.go +++ /dev/null @@ -1,829 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package client - -import ( - "archive/tar" - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "net/http/httptrace" - "net/textproto" - "net/url" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/api" - swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http" - "github.com/ethereum/go-ethereum/swarm/spancontext" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/pborman/uuid" -) - -var ( - ErrUnauthorized = errors.New("unauthorized") -) - -func NewClient(gateway string) *Client { - return &Client{ - Gateway: gateway, - } -} - -// Client wraps interaction with a swarm HTTP gateway. -type Client struct { - Gateway string -} - -// UploadRaw uploads raw data to swarm and returns the resulting hash. If toEncrypt is true it -// uploads encrypted data -func (c *Client) UploadRaw(r io.Reader, size int64, toEncrypt bool) (string, error) { - if size <= 0 { - return "", errors.New("data size must be greater than zero") - } - addr := "" - if toEncrypt { - addr = "encrypt" - } - req, err := http.NewRequest("POST", c.Gateway+"/bzz-raw:/"+addr, r) - if err != nil { - return "", err - } - req.ContentLength = size - req.Header.Set(swarmhttp.SwarmTagHeaderName, fmt.Sprintf("raw_upload_%d", time.Now().Unix())) - - res, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", err - } - return string(data), nil -} - -// DownloadRaw downloads raw data from swarm and it returns a ReadCloser and a bool whether the -// content was encrypted -func (c *Client) DownloadRaw(hash string) (io.ReadCloser, bool, error) { - uri := c.Gateway + "/bzz-raw:/" + hash - res, err := http.DefaultClient.Get(uri) - if err != nil { - return nil, false, err - } - if res.StatusCode != http.StatusOK { - res.Body.Close() - return nil, false, fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - isEncrypted := (res.Header.Get("X-Decrypted") == "true") - return res.Body, isEncrypted, nil -} - -// File represents a file in a swarm manifest and is used for uploading and -// downloading content to and from swarm -type File struct { - io.ReadCloser - api.ManifestEntry - Tag string -} - -// Open opens a local file which can then be passed to client.Upload to upload -// it to swarm -func Open(path string) (*File, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - stat, err := f.Stat() - if err != nil { - f.Close() - return nil, err - } - - contentType, err := api.DetectContentType(f.Name(), f) - if err != nil { - return nil, err - } - - return &File{ - ReadCloser: f, - ManifestEntry: api.ManifestEntry{ - ContentType: contentType, - Mode: int64(stat.Mode()), - Size: stat.Size(), - ModTime: stat.ModTime(), - }, - Tag: filepath.Base(path), - }, nil -} - -// Upload uploads a file to swarm and either adds it to an existing manifest -// (if the manifest argument is non-empty) or creates a new manifest containing -// the file, returning the resulting manifest hash (the file will then be -// available at bzz:/<hash>/<path>) -func (c *Client) Upload(file *File, manifest string, toEncrypt bool) (string, error) { - if file.Size <= 0 { - return "", errors.New("file size must be greater than zero") - } - return c.TarUpload(manifest, &FileUploader{file}, "", toEncrypt) -} - -// Download downloads a file with the given path from the swarm manifest with -// the given hash (i.e. it gets bzz:/<hash>/<path>) -func (c *Client) Download(hash, path string) (*File, error) { - uri := c.Gateway + "/bzz:/" + hash + "/" + path - res, err := http.DefaultClient.Get(uri) - if err != nil { - return nil, err - } - if res.StatusCode != http.StatusOK { - res.Body.Close() - return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - return &File{ - ReadCloser: res.Body, - ManifestEntry: api.ManifestEntry{ - ContentType: res.Header.Get("Content-Type"), - Size: res.ContentLength, - }, - }, nil -} - -// UploadDirectory uploads a directory tree to swarm and either adds the files -// to an existing manifest (if the manifest argument is non-empty) or creates a -// new manifest, returning the resulting manifest hash (files from the -// directory will then be available at bzz:/<hash>/path/to/file), with -// the file specified in defaultPath being uploaded to the root of the manifest -// (i.e. bzz:/<hash>/) -func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bool) (string, error) { - stat, err := os.Stat(dir) - if err != nil { - return "", err - } else if !stat.IsDir() { - return "", fmt.Errorf("not a directory: %s", dir) - } - if defaultPath != "" { - if _, err := os.Stat(filepath.Join(dir, defaultPath)); err != nil { - if os.IsNotExist(err) { - return "", fmt.Errorf("the default path %q was not found in the upload directory %q", defaultPath, dir) - } - return "", fmt.Errorf("default path: %v", err) - } - } - return c.TarUpload(manifest, &DirectoryUploader{dir}, defaultPath, toEncrypt) -} - -// DownloadDirectory downloads the files contained in a swarm manifest under -// the given path into a local directory (existing files will be overwritten) -func (c *Client) DownloadDirectory(hash, path, destDir, credentials string) error { - stat, err := os.Stat(destDir) - if err != nil { - return err - } else if !stat.IsDir() { - return fmt.Errorf("not a directory: %s", destDir) - } - - uri := c.Gateway + "/bzz:/" + hash + "/" + path - req, err := http.NewRequest("GET", uri, nil) - if err != nil { - return err - } - if credentials != "" { - req.SetBasicAuth("", credentials) - } - req.Header.Set("Accept", "application/x-tar") - res, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - switch res.StatusCode { - case http.StatusOK: - case http.StatusUnauthorized: - return ErrUnauthorized - default: - return fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - tr := tar.NewReader(res.Body) - for { - hdr, err := tr.Next() - if err == io.EOF { - return nil - } else if err != nil { - return err - } - // ignore the default path file - if hdr.Name == "" { - continue - } - - dstPath := filepath.Join(destDir, filepath.Clean(strings.TrimPrefix(hdr.Name, path))) - if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil { - return err - } - var mode os.FileMode = 0644 - if hdr.Mode > 0 { - mode = os.FileMode(hdr.Mode) - } - dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) - if err != nil { - return err - } - n, err := io.Copy(dst, tr) - dst.Close() - if err != nil { - return err - } else if n != hdr.Size { - return fmt.Errorf("expected %s to be %d bytes but got %d", hdr.Name, hdr.Size, n) - } - } -} - -// DownloadFile downloads a single file into the destination directory -// if the manifest entry does not specify a file name - it will fallback -// to the hash of the file as a filename -func (c *Client) DownloadFile(hash, path, dest, credentials string) error { - hasDestinationFilename := false - if stat, err := os.Stat(dest); err == nil { - hasDestinationFilename = !stat.IsDir() - } else { - if os.IsNotExist(err) { - // does not exist - should be created - hasDestinationFilename = true - } else { - return fmt.Errorf("could not stat path: %v", err) - } - } - - manifestList, err := c.List(hash, path, credentials) - if err != nil { - return err - } - - switch len(manifestList.Entries) { - case 0: - return fmt.Errorf("could not find path requested at manifest address. make sure the path you've specified is correct") - case 1: - //continue - default: - return fmt.Errorf("got too many matches for this path") - } - - uri := c.Gateway + "/bzz:/" + hash + "/" + path - req, err := http.NewRequest("GET", uri, nil) - if err != nil { - return err - } - if credentials != "" { - req.SetBasicAuth("", credentials) - } - res, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - switch res.StatusCode { - case http.StatusOK: - case http.StatusUnauthorized: - return ErrUnauthorized - default: - return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode) - } - filename := "" - if hasDestinationFilename { - filename = dest - } else { - // try to assert - re := regexp.MustCompile("[^/]+$") //everything after last slash - - if results := re.FindAllString(path, -1); len(results) > 0 { - filename = results[len(results)-1] - } else { - if entry := manifestList.Entries[0]; entry.Path != "" && entry.Path != "/" { - filename = entry.Path - } else { - // assume hash as name if there's nothing from the command line - filename = hash - } - } - filename = filepath.Join(dest, filename) - } - filePath, err := filepath.Abs(filename) - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(filePath), 0777); err != nil { - return err - } - - dst, err := os.Create(filename) - if err != nil { - return err - } - defer dst.Close() - - _, err = io.Copy(dst, res.Body) - return err -} - -// UploadManifest uploads the given manifest to swarm -func (c *Client) UploadManifest(m *api.Manifest, toEncrypt bool) (string, error) { - data, err := json.Marshal(m) - if err != nil { - return "", err - } - return c.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt) -} - -// DownloadManifest downloads a swarm manifest -func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) { - res, isEncrypted, err := c.DownloadRaw(hash) - if err != nil { - return nil, isEncrypted, err - } - defer res.Close() - var manifest api.Manifest - if err := json.NewDecoder(res).Decode(&manifest); err != nil { - return nil, isEncrypted, err - } - return &manifest, isEncrypted, nil -} - -// List list files in a swarm manifest which have the given prefix, grouping -// common prefixes using "/" as a delimiter. -// -// For example, if the manifest represents the following directory structure: -// -// file1.txt -// file2.txt -// dir1/file3.txt -// dir1/dir2/file4.txt -// -// Then: -// -// - a prefix of "" would return [dir1/, file1.txt, file2.txt] -// - a prefix of "file" would return [file1.txt, file2.txt] -// - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt] -// -// where entries ending with "/" are common prefixes. -func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, error) { - req, err := http.NewRequest(http.MethodGet, c.Gateway+"/bzz-list:/"+hash+"/"+prefix, nil) - if err != nil { - return nil, err - } - if credentials != "" { - req.SetBasicAuth("", credentials) - } - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - switch res.StatusCode { - case http.StatusOK: - case http.StatusUnauthorized: - return nil, ErrUnauthorized - default: - return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - var list api.ManifestList - if err := json.NewDecoder(res.Body).Decode(&list); err != nil { - return nil, err - } - return &list, nil -} - -// Uploader uploads files to swarm using a provided UploadFn -type Uploader interface { - Upload(UploadFn) error - Tag() string -} - -type UploaderFunc func(UploadFn) error - -func (u UploaderFunc) Upload(upload UploadFn) error { - return u(upload) -} - -func (u UploaderFunc) Tag() string { - return fmt.Sprintf("multipart_upload_%d", time.Now().Unix()) -} - -// DirectoryUploader implements Uploader -var _ Uploader = &DirectoryUploader{} - -// DirectoryUploader uploads all files in a directory, optionally uploading -// a file to the default path -type DirectoryUploader struct { - Dir string -} - -func (d *DirectoryUploader) Tag() string { - return filepath.Base(d.Dir) -} - -// Upload performs the upload of the directory and default path -func (d *DirectoryUploader) Upload(upload UploadFn) error { - return filepath.Walk(d.Dir, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - if f.IsDir() { - return nil - } - file, err := Open(path) - if err != nil { - return err - } - relPath, err := filepath.Rel(d.Dir, path) - if err != nil { - return err - } - file.Path = filepath.ToSlash(relPath) - return upload(file) - }) -} - -var _ Uploader = &FileUploader{} - -// FileUploader uploads a single file -type FileUploader struct { - File *File -} - -func (f *FileUploader) Tag() string { - return f.File.Tag -} - -// Upload performs the upload of the file -func (f *FileUploader) Upload(upload UploadFn) error { - return upload(f.File) -} - -// UploadFn is the type of function passed to an Uploader to perform the upload -// of a single file (for example, a directory uploader would call a provided -// UploadFn for each file in the directory tree) -type UploadFn func(file *File) error - -// TarUpload uses the given Uploader to upload files to swarm as a tar stream, -// returning the resulting manifest hash -func (c *Client) TarUpload(hash string, uploader Uploader, defaultPath string, toEncrypt bool) (string, error) { - ctx, sp := spancontext.StartSpan(context.Background(), "api.client.tarupload") - defer sp.Finish() - - var tn time.Time - - reqR, reqW := io.Pipe() - defer reqR.Close() - addr := hash - - // If there is a hash already (a manifest), then that manifest will determine if the upload has - // to be encrypted or not. If there is no manifest then the toEncrypt parameter decides if - // there is encryption or not. - if hash == "" && toEncrypt { - // This is the built-in address for the encrypted upload endpoint - addr = "encrypt" - } - req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+addr, reqR) - if err != nil { - return "", err - } - - trace := GetClientTrace("swarm api client - upload tar", "api.client.uploadtar", uuid.New()[:8], &tn) - - req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) - transport := http.DefaultTransport - - req.Header.Set("Content-Type", "application/x-tar") - if defaultPath != "" { - q := req.URL.Query() - q.Set("defaultpath", defaultPath) - req.URL.RawQuery = q.Encode() - } - - tag := uploader.Tag() - if tag == "" { - tag = "unnamed_tag_" + fmt.Sprintf("%d", time.Now().Unix()) - } - log.Trace("setting upload tag", "tag", tag) - - req.Header.Set(swarmhttp.SwarmTagHeaderName, tag) - - // use 'Expect: 100-continue' so we don't send the request body if - // the server refuses the request - req.Header.Set("Expect", "100-continue") - - tw := tar.NewWriter(reqW) - - // define an UploadFn which adds files to the tar stream - uploadFn := func(file *File) error { - hdr := &tar.Header{ - Name: file.Path, - Mode: file.Mode, - Size: file.Size, - ModTime: file.ModTime, - Xattrs: map[string]string{ - "user.swarm.content-type": file.ContentType, - }, - } - if err := tw.WriteHeader(hdr); err != nil { - return err - } - _, err = io.Copy(tw, file) - return err - } - - // run the upload in a goroutine so we can send the request headers and - // wait for a '100 Continue' response before sending the tar stream - go func() { - err := uploader.Upload(uploadFn) - if err == nil { - err = tw.Close() - } - reqW.CloseWithError(err) - }() - tn = time.Now() - res, err := transport.RoundTrip(req) - if err != nil { - return "", err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", err - } - return string(data), nil -} - -// MultipartUpload uses the given Uploader to upload files to swarm as a -// multipart form, returning the resulting manifest hash -func (c *Client) MultipartUpload(hash string, uploader Uploader) (string, error) { - reqR, reqW := io.Pipe() - defer reqR.Close() - req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+hash, reqR) - if err != nil { - return "", err - } - - // use 'Expect: 100-continue' so we don't send the request body if - // the server refuses the request - req.Header.Set("Expect", "100-continue") - - mw := multipart.NewWriter(reqW) - req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%q", mw.Boundary())) - req.Header.Set(swarmhttp.SwarmTagHeaderName, fmt.Sprintf("multipart_upload_%d", time.Now().Unix())) - - // define an UploadFn which adds files to the multipart form - uploadFn := func(file *File) error { - hdr := make(textproto.MIMEHeader) - hdr.Set("Content-Disposition", fmt.Sprintf("form-data; name=%q", file.Path)) - hdr.Set("Content-Type", file.ContentType) - hdr.Set("Content-Length", strconv.FormatInt(file.Size, 10)) - w, err := mw.CreatePart(hdr) - if err != nil { - return err - } - _, err = io.Copy(w, file) - return err - } - - // run the upload in a goroutine so we can send the request headers and - // wait for a '100 Continue' response before sending the multipart form - go func() { - err := uploader.Upload(uploadFn) - if err == nil { - err = mw.Close() - } - reqW.CloseWithError(err) - }() - - res, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", err - } - return string(data), nil -} - -// ErrNoFeedUpdatesFound is returned when Swarm cannot find updates of the given feed -var ErrNoFeedUpdatesFound = errors.New("No updates found for this feed") - -// CreateFeedWithManifest creates a feed manifest, initializing it with the provided -// data -// Returns the resulting feed manifest address that you can use to include in an ENS Resolver (setContent) -// or reference future updates (Client.UpdateFeed) -func (c *Client) CreateFeedWithManifest(request *feed.Request) (string, error) { - responseStream, err := c.updateFeed(request, true) - if err != nil { - return "", err - } - defer responseStream.Close() - - body, err := ioutil.ReadAll(responseStream) - if err != nil { - return "", err - } - - var manifestAddress string - if err = json.Unmarshal(body, &manifestAddress); err != nil { - return "", err - } - return manifestAddress, nil -} - -// UpdateFeed allows you to set a new version of your content -func (c *Client) UpdateFeed(request *feed.Request) error { - _, err := c.updateFeed(request, false) - return err -} - -func (c *Client) updateFeed(request *feed.Request, createManifest bool) (io.ReadCloser, error) { - URL, err := url.Parse(c.Gateway) - if err != nil { - return nil, err - } - URL.Path = "/bzz-feed:/" - values := URL.Query() - body := request.AppendValues(values) - if createManifest { - values.Set("manifest", "1") - } - URL.RawQuery = values.Encode() - - req, err := http.NewRequest("POST", URL.String(), bytes.NewBuffer(body)) - if err != nil { - return nil, err - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - return res.Body, nil -} - -// QueryFeed returns a byte stream with the raw content of the feed update -// manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver -// points to that address -func (c *Client) QueryFeed(query *feed.Query, manifestAddressOrDomain string) (io.ReadCloser, error) { - return c.queryFeed(query, manifestAddressOrDomain, false) -} - -// queryFeed returns a byte stream with the raw content of the feed update -// manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver -// points to that address -// meta set to true will instruct the node return feed metainformation instead -func (c *Client) queryFeed(query *feed.Query, manifestAddressOrDomain string, meta bool) (io.ReadCloser, error) { - URL, err := url.Parse(c.Gateway) - if err != nil { - return nil, err - } - URL.Path = "/bzz-feed:/" + manifestAddressOrDomain - values := URL.Query() - if query != nil { - query.AppendValues(values) //adds query parameters - } - if meta { - values.Set("meta", "1") - } - URL.RawQuery = values.Encode() - res, err := http.Get(URL.String()) - if err != nil { - return nil, err - } - - if res.StatusCode != http.StatusOK { - if res.StatusCode == http.StatusNotFound { - return nil, ErrNoFeedUpdatesFound - } - errorMessageBytes, err := ioutil.ReadAll(res.Body) - var errorMessage string - if err != nil { - errorMessage = "cannot retrieve error message: " + err.Error() - } else { - errorMessage = string(errorMessageBytes) - } - return nil, fmt.Errorf("Error retrieving feed updates: %s", errorMessage) - } - - return res.Body, nil -} - -// GetFeedRequest returns a structure that describes the referenced feed status -// manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver -// points to that address -func (c *Client) GetFeedRequest(query *feed.Query, manifestAddressOrDomain string) (*feed.Request, error) { - - responseStream, err := c.queryFeed(query, manifestAddressOrDomain, true) - if err != nil { - return nil, err - } - defer responseStream.Close() - - body, err := ioutil.ReadAll(responseStream) - if err != nil { - return nil, err - } - - var metadata feed.Request - if err := metadata.UnmarshalJSON(body); err != nil { - return nil, err - } - return &metadata, nil -} - -func GetClientTrace(traceMsg, metricPrefix, ruid string, tn *time.Time) *httptrace.ClientTrace { - trace := &httptrace.ClientTrace{ - GetConn: func(_ string) { - log.Trace(traceMsg+" - http get", "event", "GetConn", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".getconn", nil).Update(time.Since(*tn)) - }, - GotConn: func(_ httptrace.GotConnInfo) { - log.Trace(traceMsg+" - http get", "event", "GotConn", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".gotconn", nil).Update(time.Since(*tn)) - }, - PutIdleConn: func(err error) { - log.Trace(traceMsg+" - http get", "event", "PutIdleConn", "ruid", ruid, "err", err) - metrics.GetOrRegisterResettingTimer(metricPrefix+".putidle", nil).Update(time.Since(*tn)) - }, - GotFirstResponseByte: func() { - log.Trace(traceMsg+" - http get", "event", "GotFirstResponseByte", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".firstbyte", nil).Update(time.Since(*tn)) - }, - Got100Continue: func() { - log.Trace(traceMsg, "event", "Got100Continue", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".got100continue", nil).Update(time.Since(*tn)) - }, - DNSStart: func(_ httptrace.DNSStartInfo) { - log.Trace(traceMsg, "event", "DNSStart", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".dnsstart", nil).Update(time.Since(*tn)) - }, - DNSDone: func(_ httptrace.DNSDoneInfo) { - log.Trace(traceMsg, "event", "DNSDone", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".dnsdone", nil).Update(time.Since(*tn)) - }, - ConnectStart: func(network, addr string) { - log.Trace(traceMsg, "event", "ConnectStart", "ruid", ruid, "network", network, "addr", addr) - metrics.GetOrRegisterResettingTimer(metricPrefix+".connectstart", nil).Update(time.Since(*tn)) - }, - ConnectDone: func(network, addr string, err error) { - log.Trace(traceMsg, "event", "ConnectDone", "ruid", ruid, "network", network, "addr", addr, "err", err) - metrics.GetOrRegisterResettingTimer(metricPrefix+".connectdone", nil).Update(time.Since(*tn)) - }, - WroteHeaders: func() { - log.Trace(traceMsg, "event", "WroteHeaders(request)", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".wroteheaders", nil).Update(time.Since(*tn)) - }, - Wait100Continue: func() { - log.Trace(traceMsg, "event", "Wait100Continue", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".wait100continue", nil).Update(time.Since(*tn)) - }, - WroteRequest: func(_ httptrace.WroteRequestInfo) { - log.Trace(traceMsg, "event", "WroteRequest", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".wroterequest", nil).Update(time.Since(*tn)) - }, - } - return trace -} diff --git a/swarm/api/client/client_test.go b/swarm/api/client/client_test.go deleted file mode 100644 index 92489849c..000000000 --- a/swarm/api/client/client_test.go +++ /dev/null @@ -1,608 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package client - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "sort" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/swarm/api" - swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/ethereum/go-ethereum/swarm/storage/feed/lookup" - "github.com/ethereum/go-ethereum/swarm/testutil" -) - -func serverFunc(api *api.API) swarmhttp.TestServer { - return swarmhttp.NewServer(api, "") -} - -// TestClientUploadDownloadRaw test uploading and downloading raw data to swarm -func TestClientUploadDownloadRaw(t *testing.T) { - testClientUploadDownloadRaw(false, t) -} - -func TestClientUploadDownloadRawEncrypted(t *testing.T) { - if testutil.RaceEnabled { - t.Skip("flaky with -race on Travis") - // See: https://github.com/ethersphere/go-ethereum/issues/1254 - } - - testClientUploadDownloadRaw(true, t) -} - -func testClientUploadDownloadRaw(toEncrypt bool, t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - client := NewClient(srv.URL) - - // upload some raw data - data := []byte("foo123") - hash, err := client.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt) - if err != nil { - t.Fatal(err) - } - - // check the tag was created successfully - tag := srv.Tags.All()[0] - testutil.CheckTag(t, tag, 1, 1, 0, 1) - - // check we can download the same data - res, isEncrypted, err := client.DownloadRaw(hash) - if err != nil { - t.Fatal(err) - } - if isEncrypted != toEncrypt { - t.Fatalf("Expected encyption status %v got %v", toEncrypt, isEncrypted) - } - defer res.Close() - gotData, err := ioutil.ReadAll(res) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(gotData, data) { - t.Fatalf("expected downloaded data to be %q, got %q", data, gotData) - } -} - -// TestClientUploadDownloadFiles test uploading and downloading files to swarm -// manifests -func TestClientUploadDownloadFiles(t *testing.T) { - testClientUploadDownloadFiles(false, t) -} - -func TestClientUploadDownloadFilesEncrypted(t *testing.T) { - testClientUploadDownloadFiles(true, t) -} - -func testClientUploadDownloadFiles(toEncrypt bool, t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - client := NewClient(srv.URL) - upload := func(manifest, path string, data []byte) string { - file := &File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), - ManifestEntry: api.ManifestEntry{ - Path: path, - ContentType: "text/plain", - Size: int64(len(data)), - }, - } - hash, err := client.Upload(file, manifest, toEncrypt) - if err != nil { - t.Fatal(err) - } - return hash - } - checkDownload := func(manifest, path string, expected []byte) { - file, err := client.Download(manifest, path) - if err != nil { - t.Fatal(err) - } - defer file.Close() - if file.Size != int64(len(expected)) { - t.Fatalf("expected downloaded file to be %d bytes, got %d", len(expected), file.Size) - } - if file.ContentType != "text/plain" { - t.Fatalf("expected downloaded file to have type %q, got %q", "text/plain", file.ContentType) - } - data, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(data, expected) { - t.Fatalf("expected downloaded data to be %q, got %q", expected, data) - } - } - - // upload a file to the root of a manifest - rootData := []byte("some-data") - rootHash := upload("", "", rootData) - - // check we can download the root file - checkDownload(rootHash, "", rootData) - - // upload another file to the same manifest - otherData := []byte("some-other-data") - newHash := upload(rootHash, "some/other/path", otherData) - - // check we can download both files from the new manifest - checkDownload(newHash, "", rootData) - checkDownload(newHash, "some/other/path", otherData) - - // replace the root file with different data - newHash = upload(newHash, "", otherData) - - // check both files have the other data - checkDownload(newHash, "", otherData) - checkDownload(newHash, "some/other/path", otherData) -} - -var testDirFiles = []string{ - "file1.txt", - "file2.txt", - "dir1/file3.txt", - "dir1/file4.txt", - "dir2/file5.txt", - "dir2/dir3/file6.txt", - "dir2/dir4/file7.txt", - "dir2/dir4/file8.txt", -} - -func newTestDirectory(t *testing.T) string { - dir, err := ioutil.TempDir("", "swarm-client-test") - if err != nil { - t.Fatal(err) - } - - for _, file := range testDirFiles { - path := filepath.Join(dir, file) - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - os.RemoveAll(dir) - t.Fatalf("error creating dir for %s: %s", path, err) - } - if err := ioutil.WriteFile(path, []byte(file), 0644); err != nil { - os.RemoveAll(dir) - t.Fatalf("error writing file %s: %s", path, err) - } - } - - return dir -} - -// TestClientUploadDownloadDirectory tests uploading and downloading a -// directory of files to a swarm manifest -func TestClientUploadDownloadDirectory(t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - dir := newTestDirectory(t) - defer os.RemoveAll(dir) - - // upload the directory - client := NewClient(srv.URL) - defaultPath := testDirFiles[0] - hash, err := client.UploadDirectory(dir, defaultPath, "", false) - if err != nil { - t.Fatalf("error uploading directory: %s", err) - } - - // check the tag was created successfully - tag := srv.Tags.All()[0] - testutil.CheckTag(t, tag, 9, 9, 0, 9) - - // check we can download the individual files - checkDownloadFile := func(path string, expected []byte) { - file, err := client.Download(hash, path) - if err != nil { - t.Fatal(err) - } - defer file.Close() - data, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(data, expected) { - t.Fatalf("expected data to be %q, got %q", expected, data) - } - } - for _, file := range testDirFiles { - checkDownloadFile(file, []byte(file)) - } - - // check we can download the default path - checkDownloadFile("", []byte(testDirFiles[0])) - - // check we can download the directory - tmp, err := ioutil.TempDir("", "swarm-client-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) - if err := client.DownloadDirectory(hash, "", tmp, ""); err != nil { - t.Fatal(err) - } - for _, file := range testDirFiles { - data, err := ioutil.ReadFile(filepath.Join(tmp, file)) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(data, []byte(file)) { - t.Fatalf("expected data to be %q, got %q", file, data) - } - } -} - -// TestClientFileList tests listing files in a swarm manifest -func TestClientFileList(t *testing.T) { - testClientFileList(false, t) -} - -func TestClientFileListEncrypted(t *testing.T) { - testClientFileList(true, t) -} - -func testClientFileList(toEncrypt bool, t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - dir := newTestDirectory(t) - defer os.RemoveAll(dir) - - client := NewClient(srv.URL) - hash, err := client.UploadDirectory(dir, "", "", toEncrypt) - if err != nil { - t.Fatalf("error uploading directory: %s", err) - } - - ls := func(prefix string) []string { - list, err := client.List(hash, prefix, "") - if err != nil { - t.Fatal(err) - } - paths := make([]string, 0, len(list.CommonPrefixes)+len(list.Entries)) - paths = append(paths, list.CommonPrefixes...) - for _, entry := range list.Entries { - paths = append(paths, entry.Path) - } - sort.Strings(paths) - return paths - } - - tests := map[string][]string{ - "": {"dir1/", "dir2/", "file1.txt", "file2.txt"}, - "file": {"file1.txt", "file2.txt"}, - "file1": {"file1.txt"}, - "file2.txt": {"file2.txt"}, - "file12": {}, - "dir": {"dir1/", "dir2/"}, - "dir1": {"dir1/"}, - "dir1/": {"dir1/file3.txt", "dir1/file4.txt"}, - "dir1/file": {"dir1/file3.txt", "dir1/file4.txt"}, - "dir1/file3.txt": {"dir1/file3.txt"}, - "dir1/file34": {}, - "dir2/": {"dir2/dir3/", "dir2/dir4/", "dir2/file5.txt"}, - "dir2/file": {"dir2/file5.txt"}, - "dir2/dir": {"dir2/dir3/", "dir2/dir4/"}, - "dir2/dir3/": {"dir2/dir3/file6.txt"}, - "dir2/dir4/": {"dir2/dir4/file7.txt", "dir2/dir4/file8.txt"}, - "dir2/dir4/file": {"dir2/dir4/file7.txt", "dir2/dir4/file8.txt"}, - "dir2/dir4/file7.txt": {"dir2/dir4/file7.txt"}, - "dir2/dir4/file78": {}, - } - for prefix, expected := range tests { - actual := ls(prefix) - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("expected prefix %q to return %v, got %v", prefix, expected, actual) - } - } -} - -// TestClientMultipartUpload tests uploading files to swarm using a multipart -// upload -func TestClientMultipartUpload(t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // define an uploader which uploads testDirFiles with some data - // note: this test should result in SEEN chunks. assert accordingly - data := []byte("some-data") - uploader := UploaderFunc(func(upload UploadFn) error { - for _, name := range testDirFiles { - file := &File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), - ManifestEntry: api.ManifestEntry{ - Path: name, - ContentType: "text/plain", - Size: int64(len(data)), - }, - } - if err := upload(file); err != nil { - return err - } - } - return nil - }) - - // upload the files as a multipart upload - client := NewClient(srv.URL) - hash, err := client.MultipartUpload("", uploader) - if err != nil { - t.Fatal(err) - } - - // check the tag was created successfully - tag := srv.Tags.All()[0] - testutil.CheckTag(t, tag, 9, 9, 7, 9) - - // check we can download the individual files - checkDownloadFile := func(path string) { - file, err := client.Download(hash, path) - if err != nil { - t.Fatal(err) - } - defer file.Close() - gotData, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(gotData, data) { - t.Fatalf("expected data to be %q, got %q", data, gotData) - } - } - for _, file := range testDirFiles { - checkDownloadFile(file) - } -} - -func newTestSigner() (*feed.GenericSigner, error) { - privKey, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - if err != nil { - return nil, err - } - return feed.NewGenericSigner(privKey), nil -} - -// Test the transparent resolving of feed updates with bzz:// scheme -// -// First upload data to bzz:, and store the Swarm hash to the resulting manifest in a feed update. -// This effectively uses a feed to store a pointer to content rather than the content itself -// Retrieving the update with the Swarm hash should return the manifest pointing directly to the data -// and raw retrieve of that hash should return the data -func TestClientBzzWithFeed(t *testing.T) { - - signer, _ := newTestSigner() - - // Initialize a Swarm test server - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - swarmClient := NewClient(srv.URL) - defer srv.Close() - - // put together some data for our test: - dataBytes := []byte(` - // - // Create some data our manifest will point to. Data that could be very big and wouldn't fit in a feed update. - // So what we are going to do is upload it to Swarm bzz:// and obtain a **manifest hash** pointing to it: - // - // MANIFEST HASH --> DATA - // - // Then, we store that **manifest hash** into a Swarm Feed update. Once we have done this, - // we can use the **feed manifest hash** in bzz:// instead, this way: bzz://feed-manifest-hash. - // - // FEED MANIFEST HASH --> MANIFEST HASH --> DATA - // - // Given that we can update the feed at any time with a new **manifest hash** but the **feed manifest hash** - // stays constant, we have effectively created a fixed address to changing content. (Applause) - // - // FEED MANIFEST HASH (the same) --> MANIFEST HASH(2) --> DATA(2) - // - `) - - // Create a virtual File out of memory containing the above data - f := &File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(dataBytes)), - ManifestEntry: api.ManifestEntry{ - ContentType: "text/plain", - Mode: 0660, - Size: int64(len(dataBytes)), - }, - } - - // upload data to bzz:// and retrieve the content-addressed manifest hash, hex-encoded. - manifestAddressHex, err := swarmClient.Upload(f, "", false) - if err != nil { - t.Fatalf("Error creating manifest: %s", err) - } - - // convert the hex-encoded manifest hash to a 32-byte slice - manifestAddress := common.FromHex(manifestAddressHex) - - if len(manifestAddress) != storage.AddressLength { - t.Fatalf("Something went wrong. Got a hash of an unexpected length. Expected %d bytes. Got %d", storage.AddressLength, len(manifestAddress)) - } - - // Now create a **feed manifest**. For that, we need a topic: - topic, _ := feed.NewTopic("interesting topic indeed", nil) - - // Build a feed request to update data - request := feed.NewFirstRequest(topic) - - // Put the 32-byte address of the manifest into the feed update - request.SetData(manifestAddress) - - // Sign the update - if err := request.Sign(signer); err != nil { - t.Fatalf("Error signing update: %s", err) - } - - // Publish the update and at the same time request a **feed manifest** to be created - feedManifestAddressHex, err := swarmClient.CreateFeedWithManifest(request) - if err != nil { - t.Fatalf("Error creating feed manifest: %s", err) - } - - // Check we have received the exact **feed manifest** to be expected - // given the topic and user signing the updates: - correctFeedManifestAddrHex := "747c402e5b9dc715a25a4393147512167bab018a007fad7cdcd9adc7fce1ced2" - if feedManifestAddressHex != correctFeedManifestAddrHex { - t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctFeedManifestAddrHex, feedManifestAddressHex) - } - - // Check we get a not found error when trying to get feed updates with a made-up manifest - _, err = swarmClient.QueryFeed(nil, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") - if err != ErrNoFeedUpdatesFound { - t.Fatalf("Expected to receive ErrNoFeedUpdatesFound error. Got: %s", err) - } - - // If we query the feed directly we should get **manifest hash** back: - reader, err := swarmClient.QueryFeed(nil, correctFeedManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err := ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - - //Check that indeed the **manifest hash** is retrieved - if !bytes.Equal(manifestAddress, gotData) { - t.Fatalf("Expected: %v, got %v", manifestAddress, gotData) - } - - // Now the final test we were looking for: Use bzz://<feed-manifest> and that should resolve all manifests - // and return the original data directly: - f, err = swarmClient.Download(feedManifestAddressHex, "") - if err != nil { - t.Fatal(err) - } - gotData, err = ioutil.ReadAll(f) - if err != nil { - t.Fatal(err) - } - - // Check that we get back the original data: - if !bytes.Equal(dataBytes, gotData) { - t.Fatalf("Expected: %v, got %v", manifestAddress, gotData) - } -} - -// TestClientCreateUpdateFeed will check that feeds can be created and updated via the HTTP client. -func TestClientCreateUpdateFeed(t *testing.T) { - - signer, _ := newTestSigner() - - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - client := NewClient(srv.URL) - defer srv.Close() - - // set raw data for the feed update - databytes := []byte("En un lugar de La Mancha, de cuyo nombre no quiero acordarme...") - - // our feed topic name - topic, _ := feed.NewTopic("El Quijote", nil) - createRequest := feed.NewFirstRequest(topic) - - createRequest.SetData(databytes) - if err := createRequest.Sign(signer); err != nil { - t.Fatalf("Error signing update: %s", err) - } - - feedManifestHash, err := client.CreateFeedWithManifest(createRequest) - if err != nil { - t.Fatal(err) - } - - correctManifestAddrHex := "0e9b645ebc3da167b1d56399adc3276f7a08229301b72a03336be0e7d4b71882" - if feedManifestHash != correctManifestAddrHex { - t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctManifestAddrHex, feedManifestHash) - } - - reader, err := client.QueryFeed(nil, correctManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err := ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(databytes, gotData) { - t.Fatalf("Expected: %v, got %v", databytes, gotData) - } - - // define different data - databytes = []byte("... no ha mucho tiempo que vivÃa un hidalgo de los de lanza en astillero ...") - - updateRequest, err := client.GetFeedRequest(nil, correctManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving update request template: %s", err) - } - - updateRequest.SetData(databytes) - if err := updateRequest.Sign(signer); err != nil { - t.Fatalf("Error signing update: %s", err) - } - - if err = client.UpdateFeed(updateRequest); err != nil { - t.Fatalf("Error updating feed: %s", err) - } - - reader, err = client.QueryFeed(nil, correctManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err = ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(databytes, gotData) { - t.Fatalf("Expected: %v, got %v", databytes, gotData) - } - - // now try retrieving feed updates without a manifest - - fd := &feed.Feed{ - Topic: topic, - User: signer.Address(), - } - - lookupParams := feed.NewQueryLatest(fd, lookup.NoClue) - reader, err = client.QueryFeed(lookupParams, "") - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err = ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(databytes, gotData) { - t.Fatalf("Expected: %v, got %v", databytes, gotData) - } -} diff --git a/swarm/api/config.go b/swarm/api/config.go deleted file mode 100644 index 3a87488cc..000000000 --- a/swarm/api/config.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "crypto/ecdsa" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/contracts/ens" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/swarm/network" - "github.com/ethereum/go-ethereum/swarm/pss" - "github.com/ethereum/go-ethereum/swarm/services/swap" - "github.com/ethereum/go-ethereum/swarm/storage" -) - -const ( - DefaultHTTPListenAddr = "127.0.0.1" - DefaultHTTPPort = "8500" -) - -// separate bzz directories -// allow several bzz nodes running in parallel -type Config struct { - // serialised/persisted fields - *storage.FileStoreParams - - // LocalStore - ChunkDbPath string - DbCapacity uint64 - CacheCapacity uint - BaseKey []byte - - *network.HiveParams - Swap *swap.LocalProfile - Pss *pss.PssParams - Contract common.Address - EnsRoot common.Address - EnsAPIs []string - Path string - ListenAddr string - Port string - PublicKey string - BzzKey string - Enode *enode.Node `toml:"-"` - NetworkID uint64 - SwapEnabled bool - SyncEnabled bool - SyncingSkipCheck bool - DeliverySkipCheck bool - MaxStreamPeerServers int - LightNodeEnabled bool - BootnodeMode bool - SyncUpdateDelay time.Duration - SwapAPI string - Cors string - BzzAccount string - GlobalStoreAPI string - privateKey *ecdsa.PrivateKey -} - -//create a default config with all parameters to set to defaults -func NewConfig() (c *Config) { - - c = &Config{ - FileStoreParams: storage.NewFileStoreParams(), - HiveParams: network.NewHiveParams(), - Swap: swap.NewDefaultSwapParams(), - Pss: pss.NewPssParams(), - ListenAddr: DefaultHTTPListenAddr, - Port: DefaultHTTPPort, - Path: node.DefaultDataDir(), - EnsAPIs: nil, - EnsRoot: ens.TestNetAddress, - NetworkID: network.DefaultNetworkID, - SwapEnabled: false, - SyncEnabled: true, - SyncingSkipCheck: false, - MaxStreamPeerServers: 10000, - DeliverySkipCheck: true, - SyncUpdateDelay: 15 * time.Second, - SwapAPI: "", - } - - return -} - -//some config params need to be initialized after the complete -//config building phase is completed (e.g. due to overriding flags) -func (c *Config) Init(prvKey *ecdsa.PrivateKey, nodeKey *ecdsa.PrivateKey) error { - - // create swarm dir and record key - err := c.createAndSetPath(c.Path, prvKey) - if err != nil { - return fmt.Errorf("Error creating root swarm data directory: %v", err) - } - c.setKey(prvKey) - - // create the new enode record - // signed with the ephemeral node key - enodeParams := &network.EnodeParams{ - PrivateKey: prvKey, - EnodeKey: nodeKey, - Lightnode: c.LightNodeEnabled, - Bootnode: c.BootnodeMode, - } - c.Enode, err = network.NewEnode(enodeParams) - if err != nil { - return fmt.Errorf("Error creating enode: %v", err) - } - - // initialize components that depend on the swarm instance's private key - if c.SwapEnabled { - c.Swap.Init(c.Contract, prvKey) - } - - c.privateKey = prvKey - c.ChunkDbPath = filepath.Join(c.Path, "chunks") - c.BaseKey = common.FromHex(c.BzzKey) - - c.Pss = c.Pss.WithPrivateKey(c.privateKey) - return nil -} - -func (c *Config) ShiftPrivateKey() (privKey *ecdsa.PrivateKey) { - if c.privateKey != nil { - privKey = c.privateKey - c.privateKey = nil - } - return privKey -} - -func (c *Config) setKey(prvKey *ecdsa.PrivateKey) { - bzzkeybytes := network.PrivateKeyToBzzKey(prvKey) - pubkey := crypto.FromECDSAPub(&prvKey.PublicKey) - pubkeyhex := hexutil.Encode(pubkey) - keyhex := hexutil.Encode(bzzkeybytes) - - c.privateKey = prvKey - c.PublicKey = pubkeyhex - c.BzzKey = keyhex -} - -func (c *Config) createAndSetPath(datadirPath string, prvKey *ecdsa.PrivateKey) error { - address := crypto.PubkeyToAddress(prvKey.PublicKey) - bzzdirPath := filepath.Join(datadirPath, "bzz-"+common.Bytes2Hex(address.Bytes())) - err := os.MkdirAll(bzzdirPath, os.ModePerm) - if err != nil { - return err - } - c.Path = bzzdirPath - return nil -} diff --git a/swarm/api/config_test.go b/swarm/api/config_test.go deleted file mode 100644 index 82f29f8ea..000000000 --- a/swarm/api/config_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -func TestConfig(t *testing.T) { - - var hexprvkey = "65138b2aa745041b372153550584587da326ab440576b2a1191dd95cee30039c" - var hexnodekey = "75138b2aa745041b372153550584587da326ab440576b2a1191dd95cee30039c" - - prvkey, err := crypto.HexToECDSA(hexprvkey) - if err != nil { - t.Fatalf("failed to load private key: %v", err) - } - nodekey, err := crypto.HexToECDSA(hexnodekey) - if err != nil { - t.Fatalf("failed to load private key: %v", err) - } - - one := NewConfig() - two := NewConfig() - - if equal := reflect.DeepEqual(one, two); !equal { - t.Fatal("Two default configs are not equal") - } - - err = one.Init(prvkey, nodekey) - if err != nil { - t.Fatal(err) - } - - //the init function should set the following fields - if one.BzzKey == "" { - t.Fatal("Expected BzzKey to be set") - } - if one.PublicKey == "" { - t.Fatal("Expected PublicKey to be set") - } - if one.Swap.PayProfile.Beneficiary == (common.Address{}) && one.SwapEnabled { - t.Fatal("Failed to correctly initialize SwapParams") - } - if one.ChunkDbPath == one.Path { - t.Fatal("Failed to correctly initialize StoreParams") - } -} diff --git a/swarm/api/encrypt.go b/swarm/api/encrypt.go deleted file mode 100644 index 0d516b3d5..000000000 --- a/swarm/api/encrypt.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "encoding/binary" - "errors" - - "github.com/ethereum/go-ethereum/swarm/storage/encryption" - "golang.org/x/crypto/sha3" -) - -type RefEncryption struct { - refSize int - span []byte -} - -func NewRefEncryption(refSize int) *RefEncryption { - span := make([]byte, 8) - binary.LittleEndian.PutUint64(span, uint64(refSize)) - return &RefEncryption{ - refSize: refSize, - span: span, - } -} - -func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) { - spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewLegacyKeccak256) - encryptedSpan, err := spanEncryption.Encrypt(re.span) - if err != nil { - return nil, err - } - dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewLegacyKeccak256) - encryptedData, err := dataEncryption.Encrypt(ref) - if err != nil { - return nil, err - } - encryptedRef := make([]byte, len(ref)+8) - copy(encryptedRef[:8], encryptedSpan) - copy(encryptedRef[8:], encryptedData) - - return encryptedRef, nil -} - -func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) { - spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewLegacyKeccak256) - decryptedSpan, err := spanEncryption.Decrypt(ref[:8]) - if err != nil { - return nil, err - } - - size := binary.LittleEndian.Uint64(decryptedSpan) - if size != uint64(len(ref)-8) { - return nil, errors.New("invalid span in encrypted reference") - } - - dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewLegacyKeccak256) - decryptedRef, err := dataEncryption.Decrypt(ref[8:]) - if err != nil { - return nil, err - } - - return decryptedRef, nil -} diff --git a/swarm/api/filesystem.go b/swarm/api/filesystem.go deleted file mode 100644 index 266ef71be..000000000 --- a/swarm/api/filesystem.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "bufio" - "context" - "fmt" - "io" - "os" - "path" - "path/filepath" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/storage" -) - -const maxParallelFiles = 5 - -type FileSystem struct { - api *API -} - -func NewFileSystem(api *API) *FileSystem { - return &FileSystem{api} -} - -// Upload replicates a local directory as a manifest file and uploads it -// using FileStore store -// This function waits the chunks to be stored. -// TODO: localpath should point to a manifest -// -// DEPRECATED: Use the HTTP API instead -func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error) { - var list []*manifestTrieEntry - localpath, err := filepath.Abs(filepath.Clean(lpath)) - if err != nil { - return "", err - } - - f, err := os.Open(localpath) - if err != nil { - return "", err - } - stat, err := f.Stat() - if err != nil { - return "", err - } - - var start int - if stat.IsDir() { - start = len(localpath) - log.Debug(fmt.Sprintf("uploading '%s'", localpath)) - err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { - if (err == nil) && !info.IsDir() { - if len(path) <= start { - return fmt.Errorf("Path is too short") - } - if path[:start] != localpath { - return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) - } - entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(path)}, nil) - list = append(list, entry) - } - return err - }) - if err != nil { - return "", err - } - } else { - dir := filepath.Dir(localpath) - start = len(dir) - if len(localpath) <= start { - return "", fmt.Errorf("Path is too short") - } - if localpath[:start] != dir { - return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) - } - entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(localpath)}, nil) - list = append(list, entry) - } - - errors := make([]error, len(list)) - sem := make(chan bool, maxParallelFiles) - defer close(sem) - - for i, entry := range list { - sem <- true - go func(i int, entry *manifestTrieEntry) { - defer func() { <-sem }() - - f, err := os.Open(entry.Path) - if err != nil { - errors[i] = err - return - } - defer f.Close() - - stat, err := f.Stat() - if err != nil { - errors[i] = err - return - } - - var hash storage.Address - var wait func(context.Context) error - ctx := context.TODO() - hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt) - if err != nil { - errors[i] = err - return - } - if hash != nil { - list[i].Hash = hash.Hex() - } - if err := wait(ctx); err != nil { - errors[i] = err - return - } - - list[i].ContentType, err = DetectContentType(f.Name(), f) - if err != nil { - errors[i] = err - return - } - - }(i, entry) - } - for i := 0; i < cap(sem); i++ { - sem <- true - } - - trie := &manifestTrie{ - fileStore: fs.api.fileStore, - } - quitC := make(chan bool) - for i, entry := range list { - if errors[i] != nil { - return "", errors[i] - } - entry.Path = RegularSlashes(entry.Path[start:]) - if entry.Path == index { - ientry := newManifestTrieEntry(&ManifestEntry{ - ContentType: entry.ContentType, - }, nil) - ientry.Hash = entry.Hash - trie.addEntry(ientry, quitC) - } - trie.addEntry(entry, quitC) - } - - err2 := trie.recalcAndStore() - var hs string - if err2 == nil { - hs = trie.ref.Hex() - } - return hs, err2 -} - -// Download replicates the manifest basePath structure on the local filesystem -// under localpath -// -// DEPRECATED: Use the HTTP API instead -func (fs *FileSystem) Download(bzzpath, localpath string) error { - lpath, err := filepath.Abs(filepath.Clean(localpath)) - if err != nil { - return err - } - err = os.MkdirAll(lpath, os.ModePerm) - if err != nil { - return err - } - - //resolving host and port - uri, err := Parse(path.Join("bzz:/", bzzpath)) - if err != nil { - return err - } - addr, err := fs.api.Resolve(context.TODO(), uri.Addr) - if err != nil { - return err - } - path := uri.Path - - if len(path) > 0 { - path += "/" - } - - quitC := make(chan bool) - trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) - return err - } - - type downloadListEntry struct { - addr storage.Address - path string - } - - var list []*downloadListEntry - var mde error - - prevPath := lpath - err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { - log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) - - addr = common.Hex2Bytes(entry.Hash) - path := lpath + "/" + suffix - dir := filepath.Dir(path) - if dir != prevPath { - mde = os.MkdirAll(dir, os.ModePerm) - prevPath = dir - } - if (mde == nil) && (path != dir+"/") { - list = append(list, &downloadListEntry{addr: addr, path: path}) - } - }) - if err != nil { - return err - } - - wg := sync.WaitGroup{} - errC := make(chan error) - done := make(chan bool, maxParallelFiles) - for i, entry := range list { - select { - case done <- true: - wg.Add(1) - case <-quitC: - return fmt.Errorf("aborted") - } - go func(i int, entry *downloadListEntry) { - defer wg.Done() - err := retrieveToFile(quitC, fs.api.fileStore, entry.addr, entry.path) - if err != nil { - select { - case errC <- err: - case <-quitC: - } - return - } - <-done - }(i, entry) - } - go func() { - wg.Wait() - close(errC) - }() - select { - case err = <-errC: - return err - case <-quitC: - return fmt.Errorf("aborted") - } -} - -func retrieveToFile(quitC chan bool, fileStore *storage.FileStore, addr storage.Address, path string) error { - f, err := os.Create(path) // TODO: basePath separators - if err != nil { - return err - } - reader, _ := fileStore.Retrieve(context.TODO(), addr) - writer := bufio.NewWriter(f) - size, err := reader.Size(context.TODO(), quitC) - if err != nil { - return err - } - if _, err = io.CopyN(writer, reader, size); err != nil { - return err - } - if err := writer.Flush(); err != nil { - return err - } - return f.Close() -} diff --git a/swarm/api/filesystem_test.go b/swarm/api/filesystem_test.go deleted file mode 100644 index b8f37fdd5..000000000 --- a/swarm/api/filesystem_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "bytes" - "context" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/storage" -) - -var testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test") - -func testFileSystem(t *testing.T, f func(*FileSystem, bool)) { - testAPI(t, func(api *API, _ *chunk.Tags, toEncrypt bool) { - f(NewFileSystem(api), toEncrypt) - }) -} - -func readPath(t *testing.T, parts ...string) string { - file := filepath.Join(parts...) - content, err := ioutil.ReadFile(file) - - if err != nil { - t.Fatalf("unexpected error reading '%v': %v", file, err) - } - return string(content) -} - -func TestApiDirUpload0(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "", toEncrypt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "index.html") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - - content = readPath(t, "testdata", "test0", "index.css") - resp = testGet(t, api, bzzhash, "index.css") - exp = expResponse(content, "text/css; charset=utf-8", 0) - checkResponse(t, resp, exp) - - addr := storage.Address(common.Hex2Bytes(bzzhash)) - _, _, _, _, err = api.Get(context.TODO(), NOOPDecrypt, addr, "") - if err == nil { - t.Fatalf("expected error: %v", err) - } - - downloadDir := filepath.Join(testDownloadDir, "test0") - defer os.RemoveAll(downloadDir) - err = fs.Download(bzzhash, downloadDir) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - newbzzhash, err := fs.Upload(downloadDir, "", toEncrypt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - // TODO: currently the hash is not deterministic in the encrypted case - if !toEncrypt && bzzhash != newbzzhash { - t.Fatalf("download %v reuploaded has incorrect hash, expected %v, got %v", downloadDir, bzzhash, newbzzhash) - } - }) -} - -func TestApiDirUploadModify(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - addr := storage.Address(common.Hex2Bytes(bzzhash)) - addr, err = api.Modify(context.TODO(), addr, "index.html", "", "") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - index, err := ioutil.ReadFile(filepath.Join("testdata", "test0", "index.html")) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - ctx := context.TODO() - hash, wait, err := api.Store(ctx, bytes.NewReader(index), int64(len(index)), toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - err = wait(ctx) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - addr, err = api.Modify(context.TODO(), addr, "index2.html", hash.Hex(), "text/html; charset=utf-8") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - addr, err = api.Modify(context.TODO(), addr, "img/logo.png", hash.Hex(), "text/html; charset=utf-8") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash = addr.Hex() - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "index2.html") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - - resp = testGet(t, api, bzzhash, "img/logo.png") - exp = expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - - content = readPath(t, "testdata", "test0", "index.css") - resp = testGet(t, api, bzzhash, "index.css") - exp = expResponse(content, "text/css; charset=utf-8", 0) - checkResponse(t, resp, exp) - - _, _, _, _, err = api.Get(context.TODO(), nil, addr, "") - if err == nil { - t.Errorf("expected error: %v", err) - } - }) -} - -func TestApiDirUploadWithRootFile(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "index.html", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - }) -} - -func TestApiFileUpload(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "index.html") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - }) -} - -func TestApiFileUploadWithRootFile(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "index.html", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - }) -} diff --git a/swarm/api/gen_mime.go b/swarm/api/gen_mime.go deleted file mode 100644 index 109edeb50..000000000 --- a/swarm/api/gen_mime.go +++ /dev/null @@ -1,1201 +0,0 @@ -// Code generated by github.com/ethereum/go-ethereum/cmd/swarm/mimegen. DO NOT EDIT. - -package api - -import "mime" - -func init() { - var mimeTypes = map[string]string{ - ".a2l": "application/A2L", - ".aml": "application/AML", - ".ez": "application/andrew-inset", - ".atf": "application/ATF", - ".atfx": "application/ATFX", - ".atxml": "application/ATXML", - ".atom": "application/atom+xml", - ".atomcat": "application/atomcat+xml", - ".atomdeleted": "application/atomdeleted+xml", - ".atomsvc": "application/atomsvc+xml", - ".apxml": "application/auth-policy+xml", - ".xdd": "application/bacnet-xdd+zip", - ".xcs": "application/calendar+xml", - ".cbor": "application/cbor", - ".ccmp": "application/ccmp+xml", - ".ccxml": "application/ccxml+xml", - ".cdfx": "application/CDFX+XML", - ".cdmia": "application/cdmi-capability", - ".cdmic": "application/cdmi-container", - ".cdmid": "application/cdmi-domain", - ".cdmio": "application/cdmi-object", - ".cdmiq": "application/cdmi-queue", - ".cea": "application/CEA", - ".cellml": "application/cellml+xml", - ".cml": "application/cellml+xml", - ".clue": "application/clue_info+xml", - ".cmsc": "application/cms", - ".cpl": "application/cpl+xml", - ".csrattrs": "application/csrattrs", - ".mpd": "application/dash+xml", - ".mpdd": "application/dashdelta", - ".davmount": "application/davmount+xml", - ".dcd": "application/DCD", - ".dcm": "application/dicom", - ".dii": "application/DII", - ".dit": "application/DIT", - ".xmls": "application/dskpp+xml", - ".dssc": "application/dssc+der", - ".xdssc": "application/dssc+xml", - ".dvc": "application/dvcs", - ".es": "application/ecmascript", - ".efi": "application/efi", - ".emma": "application/emma+xml", - ".emotionml": "application/emotionml+xml", - ".epub": "application/epub+zip", - ".exi": "application/exi", - ".finf": "application/fastinfoset", - ".fdt": "application/fdt+xml", - ".pfr": "application/font-tdpfr", - ".geojson": "application/geo+json", - ".gml": "application/gml+xml", - ".gz": "application/gzip", - ".tgz": "application/gzip", - ".stk": "application/hyperstudio", - ".ink": "application/inkml+xml", - ".inkml": "application/inkml+xml", - ".ipfix": "application/ipfix", - ".its": "application/its+xml", - ".js": "application/javascript", - ".jrd": "application/jrd+json", - ".json": "application/json", - ".json-patch": "application/json-patch+json", - ".jsonld": "application/ld+json", - ".lgr": "application/lgr+xml", - ".wlnk": "application/link-format", - ".lostxml": "application/lost+xml", - ".lostsyncxml": "application/lostsync+xml", - ".lxf": "application/LXF", - ".hqx": "application/mac-binhex40", - ".mads": "application/mads+xml", - ".mrc": "application/marc", - ".mrcx": "application/marcxml+xml", - ".nb": "application/mathematica", - ".ma": "application/mathematica", - ".mb": "application/mathematica", - ".mml": "application/mathml+xml", - ".mbox": "application/mbox", - ".meta4": "application/metalink4+xml", - ".mets": "application/mets+xml", - ".mf4": "application/MF4", - ".mods": "application/mods+xml", - ".m21": "application/mp21", - ".mp21": "application/mp21", - ".doc": "application/msword", - ".mxf": "application/mxf", - ".nq": "application/n-quads", - ".nt": "application/n-triples", - ".orq": "application/ocsp-request", - ".ors": "application/ocsp-response", - ".bin": "application/octet-stream", - ".lha": "application/octet-stream", - ".lzh": "application/octet-stream", - ".exe": "application/octet-stream", - ".class": "application/octet-stream", - ".so": "application/octet-stream", - ".dll": "application/octet-stream", - ".img": "application/octet-stream", - ".iso": "application/octet-stream", - ".oda": "application/oda", - ".odx": "application/ODX", - ".opf": "application/oebps-package+xml", - ".ogx": "application/ogg", - ".oxps": "application/oxps", - ".relo": "application/p2p-overlay+xml", - ".pdf": "application/pdf", - ".pdx": "application/PDX", - ".pgp": "application/pgp-encrypted", - ".sig": "application/pgp-signature", - ".p10": "application/pkcs10", - ".p12": "application/pkcs12", - ".pfx": "application/pkcs12", - ".p7m": "application/pkcs7-mime", - ".p7c": "application/pkcs7-mime", - ".p7s": "application/pkcs7-signature", - ".p8": "application/pkcs8", - ".cer": "application/pkix-cert", - ".crl": "application/pkix-crl", - ".pkipath": "application/pkix-pkipath", - ".pki": "application/pkixcmp", - ".pls": "application/pls+xml", - ".ps": "application/postscript", - ".eps": "application/postscript", - ".ai": "application/postscript", - ".provx": "application/provenance+xml", - ".cw": "application/prs.cww", - ".cww": "application/prs.cww", - ".hpub": "application/prs.hpub+zip", - ".rnd": "application/prs.nprend", - ".rct": "application/prs.nprend", - ".rdf-crypt": "application/prs.rdf-xml-crypt", - ".xsf": "application/prs.xsf+xml", - ".pskcxml": "application/pskc+xml", - ".rdf": "application/rdf+xml", - ".rif": "application/reginfo+xml", - ".rnc": "application/relax-ng-compact-syntax", - ".rld": "application/resource-lists-diff+xml", - ".rl": "application/resource-lists+xml", - ".rfcxml": "application/rfc+xml", - ".rs": "application/rls-services+xml", - ".gbr": "application/rpki-ghostbusters", - ".mft": "application/rpki-manifest", - ".roa": "application/rpki-roa", - ".rtf": "application/rtf", - ".scim": "application/scim+json", - ".scq": "application/scvp-cv-request", - ".scs": "application/scvp-cv-response", - ".spq": "application/scvp-vp-request", - ".spp": "application/scvp-vp-response", - ".sdp": "application/sdp", - ".soc": "application/sgml-open-catalog", - ".shf": "application/shf+xml", - ".siv": "application/sieve", - ".sieve": "application/sieve", - ".cl": "application/simple-filter+xml", - ".smil": "application/smil+xml", - ".smi": "application/smil+xml", - ".sml": "application/smil+xml", - ".rq": "application/sparql-query", - ".srx": "application/sparql-results+xml", - ".sql": "application/sql", - ".gram": "application/srgs", - ".grxml": "application/srgs+xml", - ".sru": "application/sru+xml", - ".ssml": "application/ssml+xml", - ".tau": "application/tamp-apex-update", - ".auc": "application/tamp-apex-update-confirm", - ".tcu": "application/tamp-community-update", - ".cuc": "application/tamp-community-update-confirm", - ".ter": "application/tamp-error", - ".tsa": "application/tamp-sequence-adjust", - ".sac": "application/tamp-sequence-adjust-confirm", - ".tur": "application/tamp-update", - ".tuc": "application/tamp-update-confirm", - ".tei": "application/tei+xml", - ".teiCorpus": "application/tei+xml", - ".odd": "application/tei+xml", - ".tfi": "application/thraud+xml", - ".tsq": "application/timestamp-query", - ".tsr": "application/timestamp-reply", - ".tsd": "application/timestamped-data", - ".trig": "application/trig", - ".ttml": "application/ttml+xml", - ".gsheet": "application/urc-grpsheet+xml", - ".rsheet": "application/urc-ressheet+xml", - ".td": "application/urc-targetdesc+xml", - ".uis": "application/urc-uisocketdesc+xml", - ".plb": "application/vnd.3gpp.pic-bw-large", - ".psb": "application/vnd.3gpp.pic-bw-small", - ".pvb": "application/vnd.3gpp.pic-bw-var", - ".sms": "application/vnd.3gpp2.sms", - ".tcap": "application/vnd.3gpp2.tcap", - ".imgcal": "application/vnd.3lightssoftware.imagescal", - ".pwn": "application/vnd.3M.Post-it-Notes", - ".aso": "application/vnd.accpac.simply.aso", - ".imp": "application/vnd.accpac.simply.imp", - ".acu": "application/vnd.acucobol", - ".atc": "application/vnd.acucorp", - ".acutc": "application/vnd.acucorp", - ".swf": "application/vnd.adobe.flash.movie", - ".fcdt": "application/vnd.adobe.formscentral.fcdt", - ".fxp": "application/vnd.adobe.fxp", - ".fxpl": "application/vnd.adobe.fxp", - ".xdp": "application/vnd.adobe.xdp+xml", - ".xfdf": "application/vnd.adobe.xfdf", - ".ahead": "application/vnd.ahead.space", - ".azf": "application/vnd.airzip.filesecure.azf", - ".azs": "application/vnd.airzip.filesecure.azs", - ".azw3": "application/vnd.amazon.mobi8-ebook", - ".acc": "application/vnd.americandynamics.acc", - ".ami": "application/vnd.amiga.ami", - ".apkg": "application/vnd.anki", - ".cii": "application/vnd.anser-web-certificate-issue-initiation", - ".fti": "application/vnd.anser-web-funds-transfer-initiation", - ".dist": "application/vnd.apple.installer+xml", - ".distz": "application/vnd.apple.installer+xml", - ".pkg": "application/vnd.apple.installer+xml", - ".mpkg": "application/vnd.apple.installer+xml", - ".m3u8": "application/vnd.apple.mpegurl", - ".swi": "application/vnd.aristanetworks.swi", - ".iota": "application/vnd.astraea-software.iota", - ".aep": "application/vnd.audiograph", - ".package": "application/vnd.autopackage", - ".bmml": "application/vnd.balsamiq.bmml+xml", - ".bmpr": "application/vnd.balsamiq.bmpr", - ".mpm": "application/vnd.blueice.multipass", - ".ep": "application/vnd.bluetooth.ep.oob", - ".le": "application/vnd.bluetooth.le.oob", - ".bmi": "application/vnd.bmi", - ".rep": "application/vnd.businessobjects", - ".tlclient": "application/vnd.cendio.thinlinc.clientconf", - ".cdxml": "application/vnd.chemdraw+xml", - ".pgn": "application/vnd.chess-pgn", - ".mmd": "application/vnd.chipnuts.karaoke-mmd", - ".cdy": "application/vnd.cinderella", - ".csl": "application/vnd.citationstyles.style+xml", - ".cla": "application/vnd.claymore", - ".rp9": "application/vnd.cloanto.rp9", - ".c4g": "application/vnd.clonk.c4group", - ".c4d": "application/vnd.clonk.c4group", - ".c4f": "application/vnd.clonk.c4group", - ".c4p": "application/vnd.clonk.c4group", - ".c4u": "application/vnd.clonk.c4group", - ".c11amc": "application/vnd.cluetrust.cartomobile-config", - ".c11amz": "application/vnd.cluetrust.cartomobile-config-pkg", - ".coffee": "application/vnd.coffeescript", - ".cbz": "application/vnd.comicbook+zip", - ".ica": "application/vnd.commerce-battelle", - ".icf": "application/vnd.commerce-battelle", - ".icd": "application/vnd.commerce-battelle", - ".ic0": "application/vnd.commerce-battelle", - ".ic1": "application/vnd.commerce-battelle", - ".ic2": "application/vnd.commerce-battelle", - ".ic3": "application/vnd.commerce-battelle", - ".ic4": "application/vnd.commerce-battelle", - ".ic5": "application/vnd.commerce-battelle", - ".ic6": "application/vnd.commerce-battelle", - ".ic7": "application/vnd.commerce-battelle", - ".ic8": "application/vnd.commerce-battelle", - ".csp": "application/vnd.commonspace", - ".cst": "application/vnd.commonspace", - ".cdbcmsg": "application/vnd.contact.cmsg", - ".ign": "application/vnd.coreos.ignition+json", - ".ignition": "application/vnd.coreos.ignition+json", - ".cmc": "application/vnd.cosmocaller", - ".clkx": "application/vnd.crick.clicker", - ".clkk": "application/vnd.crick.clicker.keyboard", - ".clkp": "application/vnd.crick.clicker.palette", - ".clkt": "application/vnd.crick.clicker.template", - ".clkw": "application/vnd.crick.clicker.wordbank", - ".wbs": "application/vnd.criticaltools.wbs+xml", - ".pml": "application/vnd.ctc-posml", - ".ppd": "application/vnd.cups-ppd", - ".curl": "application/vnd.curl", - ".dart": "application/vnd.dart", - ".rdz": "application/vnd.data-vision.rdz", - ".deb": "application/vnd.debian.binary-package", - ".udeb": "application/vnd.debian.binary-package", - ".uvf": "application/vnd.dece.data", - ".uvvf": "application/vnd.dece.data", - ".uvd": "application/vnd.dece.data", - ".uvvd": "application/vnd.dece.data", - ".uvt": "application/vnd.dece.ttml+xml", - ".uvvt": "application/vnd.dece.ttml+xml", - ".uvx": "application/vnd.dece.unspecified", - ".uvvx": "application/vnd.dece.unspecified", - ".uvz": "application/vnd.dece.zip", - ".uvvz": "application/vnd.dece.zip", - ".fe_launch": "application/vnd.denovo.fcselayout-link", - ".dsm": "application/vnd.desmume.movie", - ".dna": "application/vnd.dna", - ".docjson": "application/vnd.document+json", - ".scld": "application/vnd.doremir.scorecloud-binary-document", - ".dpg": "application/vnd.dpgraph", - ".mwc": "application/vnd.dpgraph", - ".dpgraph": "application/vnd.dpgraph", - ".dfac": "application/vnd.dreamfactory", - ".fla": "application/vnd.dtg.local.flash", - ".ait": "application/vnd.dvb.ait", - ".svc": "application/vnd.dvb.service", - ".geo": "application/vnd.dynageo", - ".dzr": "application/vnd.dzr", - ".mag": "application/vnd.ecowin.chart", - ".nml": "application/vnd.enliven", - ".esf": "application/vnd.epson.esf", - ".msf": "application/vnd.epson.msf", - ".qam": "application/vnd.epson.quickanime", - ".slt": "application/vnd.epson.salt", - ".ssf": "application/vnd.epson.ssf", - ".qcall": "application/vnd.ericsson.quickcall", - ".qca": "application/vnd.ericsson.quickcall", - ".espass": "application/vnd.espass-espass+zip", - ".es3": "application/vnd.eszigno3+xml", - ".et3": "application/vnd.eszigno3+xml", - ".asice": "application/vnd.etsi.asic-e+zip", - ".sce": "application/vnd.etsi.asic-e+zip", - ".asics": "application/vnd.etsi.asic-s+zip", - ".tst": "application/vnd.etsi.timestamp-token", - ".ez2": "application/vnd.ezpix-album", - ".ez3": "application/vnd.ezpix-package", - ".dim": "application/vnd.fastcopy-disk-image", - ".fdf": "application/vnd.fdf", - ".msd": "application/vnd.fdsn.mseed", - ".mseed": "application/vnd.fdsn.mseed", - ".seed": "application/vnd.fdsn.seed", - ".dataless": "application/vnd.fdsn.seed", - ".zfc": "application/vnd.filmit.zfc", - ".gph": "application/vnd.FloGraphIt", - ".ftc": "application/vnd.fluxtime.clip", - ".sfd": "application/vnd.font-fontforge-sfd", - ".fm": "application/vnd.framemaker", - ".fnc": "application/vnd.frogans.fnc", - ".ltf": "application/vnd.frogans.ltf", - ".fsc": "application/vnd.fsc.weblaunch", - ".oas": "application/vnd.fujitsu.oasys", - ".oa2": "application/vnd.fujitsu.oasys2", - ".oa3": "application/vnd.fujitsu.oasys3", - ".fg5": "application/vnd.fujitsu.oasysgp", - ".bh2": "application/vnd.fujitsu.oasysprs", - ".ddd": "application/vnd.fujixerox.ddd", - ".xdw": "application/vnd.fujixerox.docuworks", - ".xbd": "application/vnd.fujixerox.docuworks.binder", - ".xct": "application/vnd.fujixerox.docuworks.container", - ".fzs": "application/vnd.fuzzysheet", - ".txd": "application/vnd.genomatix.tuxedo", - ".g3": "application/vnd.geocube+xml", - ".g³": "application/vnd.geocube+xml", - ".ggb": "application/vnd.geogebra.file", - ".ggt": "application/vnd.geogebra.tool", - ".gex": "application/vnd.geometry-explorer", - ".gre": "application/vnd.geometry-explorer", - ".gxt": "application/vnd.geonext", - ".g2w": "application/vnd.geoplan", - ".g3w": "application/vnd.geospace", - ".gmx": "application/vnd.gmx", - ".kml": "application/vnd.google-earth.kml+xml", - ".kmz": "application/vnd.google-earth.kmz", - ".gqf": "application/vnd.grafeq", - ".gqs": "application/vnd.grafeq", - ".gac": "application/vnd.groove-account", - ".ghf": "application/vnd.groove-help", - ".gim": "application/vnd.groove-identity-message", - ".grv": "application/vnd.groove-injector", - ".gtm": "application/vnd.groove-tool-message", - ".tpl": "application/vnd.groove-tool-template", - ".vcg": "application/vnd.groove-vcard", - ".hal": "application/vnd.hal+xml", - ".zmm": "application/vnd.HandHeld-Entertainment+xml", - ".hbci": "application/vnd.hbci", - ".hbc": "application/vnd.hbci", - ".kom": "application/vnd.hbci", - ".upa": "application/vnd.hbci", - ".pkd": "application/vnd.hbci", - ".bpd": "application/vnd.hbci", - ".hdt": "application/vnd.hdt", - ".les": "application/vnd.hhe.lesson-player", - ".hpgl": "application/vnd.hp-HPGL", - ".hpi": "application/vnd.hp-hpid", - ".hpid": "application/vnd.hp-hpid", - ".hps": "application/vnd.hp-hps", - ".jlt": "application/vnd.hp-jlyt", - ".pcl": "application/vnd.hp-PCL", - ".sfd-hdstx": "application/vnd.hydrostatix.sof-data", - ".x3d": "application/vnd.hzn-3d-crossword", - ".emm": "application/vnd.ibm.electronic-media", - ".mpy": "application/vnd.ibm.MiniPay", - ".list3820": "application/vnd.ibm.modcap", - ".listafp": "application/vnd.ibm.modcap", - ".afp": "application/vnd.ibm.modcap", - ".pseg3820": "application/vnd.ibm.modcap", - ".irm": "application/vnd.ibm.rights-management", - ".sc": "application/vnd.ibm.secure-container", - ".icc": "application/vnd.iccprofile", - ".icm": "application/vnd.iccprofile", - ".1905.1": "application/vnd.ieee.1905", - ".igl": "application/vnd.igloader", - ".imf": "application/vnd.imagemeter.folder+zip", - ".imi": "application/vnd.imagemeter.image+zip", - ".ivp": "application/vnd.immervision-ivp", - ".ivu": "application/vnd.immervision-ivu", - ".imscc": "application/vnd.ims.imsccv1p1", - ".igm": "application/vnd.insors.igm", - ".xpw": "application/vnd.intercon.formnet", - ".xpx": "application/vnd.intercon.formnet", - ".i2g": "application/vnd.intergeo", - ".qbo": "application/vnd.intu.qbo", - ".qfx": "application/vnd.intu.qfx", - ".rcprofile": "application/vnd.ipunplugged.rcprofile", - ".irp": "application/vnd.irepository.package+xml", - ".xpr": "application/vnd.is-xpr", - ".fcs": "application/vnd.isac.fcs", - ".jam": "application/vnd.jam", - ".rms": "application/vnd.jcp.javame.midlet-rms", - ".jisp": "application/vnd.jisp", - ".joda": "application/vnd.joost.joda-archive", - ".ktz": "application/vnd.kahootz", - ".ktr": "application/vnd.kahootz", - ".karbon": "application/vnd.kde.karbon", - ".chrt": "application/vnd.kde.kchart", - ".kfo": "application/vnd.kde.kformula", - ".flw": "application/vnd.kde.kivio", - ".kon": "application/vnd.kde.kontour", - ".kpr": "application/vnd.kde.kpresenter", - ".kpt": "application/vnd.kde.kpresenter", - ".ksp": "application/vnd.kde.kspread", - ".kwd": "application/vnd.kde.kword", - ".kwt": "application/vnd.kde.kword", - ".htke": "application/vnd.kenameaapp", - ".kia": "application/vnd.kidspiration", - ".kne": "application/vnd.Kinar", - ".knp": "application/vnd.Kinar", - ".sdf": "application/vnd.Kinar", - ".skp": "application/vnd.koan", - ".skd": "application/vnd.koan", - ".skm": "application/vnd.koan", - ".skt": "application/vnd.koan", - ".sse": "application/vnd.kodak-descriptor", - ".lasjson": "application/vnd.las.las+json", - ".lasxml": "application/vnd.las.las+xml", - ".lbd": "application/vnd.llamagraphics.life-balance.desktop", - ".lbe": "application/vnd.llamagraphics.life-balance.exchange+xml", - ".123": "application/vnd.lotus-1-2-3", - ".wk4": "application/vnd.lotus-1-2-3", - ".wk3": "application/vnd.lotus-1-2-3", - ".wk1": "application/vnd.lotus-1-2-3", - ".apr": "application/vnd.lotus-approach", - ".vew": "application/vnd.lotus-approach", - ".prz": "application/vnd.lotus-freelance", - ".pre": "application/vnd.lotus-freelance", - ".nsf": "application/vnd.lotus-notes", - ".ntf": "application/vnd.lotus-notes", - ".ndl": "application/vnd.lotus-notes", - ".ns4": "application/vnd.lotus-notes", - ".ns3": "application/vnd.lotus-notes", - ".ns2": "application/vnd.lotus-notes", - ".nsh": "application/vnd.lotus-notes", - ".nsg": "application/vnd.lotus-notes", - ".or3": "application/vnd.lotus-organizer", - ".or2": "application/vnd.lotus-organizer", - ".org": "application/vnd.lotus-organizer", - ".scm": "application/vnd.lotus-screencam", - ".lwp": "application/vnd.lotus-wordpro", - ".sam": "application/vnd.lotus-wordpro", - ".portpkg": "application/vnd.macports.portpkg", - ".mvt": "application/vnd.mapbox-vector-tile", - ".mdc": "application/vnd.marlin.drm.mdcf", - ".mmdb": "application/vnd.maxmind.maxmind-db", - ".mcd": "application/vnd.mcd", - ".mc1": "application/vnd.medcalcdata", - ".cdkey": "application/vnd.mediastation.cdkey", - ".mwf": "application/vnd.MFER", - ".mfm": "application/vnd.mfmp", - ".flo": "application/vnd.micrografx.flo", - ".igx": "application/vnd.micrografx.igx", - ".mif": "application/vnd.mif", - ".daf": "application/vnd.Mobius.DAF", - ".dis": "application/vnd.Mobius.DIS", - ".mbk": "application/vnd.Mobius.MBK", - ".mqy": "application/vnd.Mobius.MQY", - ".msl": "application/vnd.Mobius.MSL", - ".plc": "application/vnd.Mobius.PLC", - ".txf": "application/vnd.Mobius.TXF", - ".mpn": "application/vnd.mophun.application", - ".mpc": "application/vnd.mophun.certificate", - ".xul": "application/vnd.mozilla.xul+xml", - ".3mf": "application/vnd.ms-3mfdocument", - ".cil": "application/vnd.ms-artgalry", - ".asf": "application/vnd.ms-asf", - ".cab": "application/vnd.ms-cab-compressed", - ".xls": "application/vnd.ms-excel", - ".xlm": "application/vnd.ms-excel", - ".xla": "application/vnd.ms-excel", - ".xlc": "application/vnd.ms-excel", - ".xlt": "application/vnd.ms-excel", - ".xlw": "application/vnd.ms-excel", - ".xltm": "application/vnd.ms-excel.template.macroEnabled.12", - ".xlam": "application/vnd.ms-excel.addin.macroEnabled.12", - ".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12", - ".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12", - ".eot": "application/vnd.ms-fontobject", - ".chm": "application/vnd.ms-htmlhelp", - ".ims": "application/vnd.ms-ims", - ".lrm": "application/vnd.ms-lrm", - ".thmx": "application/vnd.ms-officetheme", - ".ppt": "application/vnd.ms-powerpoint", - ".pps": "application/vnd.ms-powerpoint", - ".pot": "application/vnd.ms-powerpoint", - ".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12", - ".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12", - ".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12", - ".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", - ".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12", - ".mpp": "application/vnd.ms-project", - ".mpt": "application/vnd.ms-project", - ".tnef": "application/vnd.ms-tnef", - ".tnf": "application/vnd.ms-tnef", - ".docm": "application/vnd.ms-word.document.macroEnabled.12", - ".dotm": "application/vnd.ms-word.template.macroEnabled.12", - ".wcm": "application/vnd.ms-works", - ".wdb": "application/vnd.ms-works", - ".wks": "application/vnd.ms-works", - ".wps": "application/vnd.ms-works", - ".wpl": "application/vnd.ms-wpl", - ".xps": "application/vnd.ms-xpsdocument", - ".msa": "application/vnd.msa-disk-image", - ".mseq": "application/vnd.mseq", - ".crtr": "application/vnd.multiad.creator", - ".cif": "application/vnd.multiad.creator.cif", - ".mus": "application/vnd.musician", - ".msty": "application/vnd.muvee.style", - ".taglet": "application/vnd.mynfc", - ".entity": "application/vnd.nervana", - ".request": "application/vnd.nervana", - ".bkm": "application/vnd.nervana", - ".kcm": "application/vnd.nervana", - ".nitf": "application/vnd.nitf", - ".nlu": "application/vnd.neurolanguage.nlu", - ".nds": "application/vnd.nintendo.nitro.rom", - ".sfc": "application/vnd.nintendo.snes.rom", - ".smc": "application/vnd.nintendo.snes.rom", - ".nnd": "application/vnd.noblenet-directory", - ".nns": "application/vnd.noblenet-sealer", - ".nnw": "application/vnd.noblenet-web", - ".ac": "application/vnd.nokia.n-gage.ac+xml", - ".ngdat": "application/vnd.nokia.n-gage.data", - ".n-gage": "application/vnd.nokia.n-gage.symbian.install", - ".rpst": "application/vnd.nokia.radio-preset", - ".rpss": "application/vnd.nokia.radio-presets", - ".edm": "application/vnd.novadigm.EDM", - ".edx": "application/vnd.novadigm.EDX", - ".ext": "application/vnd.novadigm.EXT", - ".odc": "application/vnd.oasis.opendocument.chart", - ".otc": "application/vnd.oasis.opendocument.chart-template", - ".odb": "application/vnd.oasis.opendocument.database", - ".odf": "application/vnd.oasis.opendocument.formula", - ".odg": "application/vnd.oasis.opendocument.graphics", - ".otg": "application/vnd.oasis.opendocument.graphics-template", - ".odi": "application/vnd.oasis.opendocument.image", - ".oti": "application/vnd.oasis.opendocument.image-template", - ".odp": "application/vnd.oasis.opendocument.presentation", - ".otp": "application/vnd.oasis.opendocument.presentation-template", - ".ods": "application/vnd.oasis.opendocument.spreadsheet", - ".ots": "application/vnd.oasis.opendocument.spreadsheet-template", - ".odt": "application/vnd.oasis.opendocument.text", - ".odm": "application/vnd.oasis.opendocument.text-master", - ".ott": "application/vnd.oasis.opendocument.text-template", - ".oth": "application/vnd.oasis.opendocument.text-web", - ".xo": "application/vnd.olpc-sugar", - ".dd2": "application/vnd.oma.dd2+xml", - ".tam": "application/vnd.onepager", - ".tamp": "application/vnd.onepagertamp", - ".tamx": "application/vnd.onepagertamx", - ".tat": "application/vnd.onepagertat", - ".tatp": "application/vnd.onepagertatp", - ".tatx": "application/vnd.onepagertatx", - ".obgx": "application/vnd.openblox.game+xml", - ".obg": "application/vnd.openblox.game-binary", - ".oeb": "application/vnd.openeye.oeb", - ".oxt": "application/vnd.openofficeorg.extension", - ".osm": "application/vnd.openstreetmap.data+xml", - ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", - ".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide", - ".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow", - ".potx": "application/vnd.openxmlformats-officedocument.presentationml.template", - ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template", - ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - ".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template", - ".ndc": "application/vnd.osa.netdeploy", - ".mgp": "application/vnd.osgeo.mapguide.package", - ".dp": "application/vnd.osgi.dp", - ".esa": "application/vnd.osgi.subsystem", - ".oxlicg": "application/vnd.oxli.countgraph", - ".prc": "application/vnd.palm", - ".pdb": "application/vnd.palm", - ".pqa": "application/vnd.palm", - ".oprc": "application/vnd.palm", - ".plp": "application/vnd.panoply", - ".paw": "application/vnd.pawaafile", - ".str": "application/vnd.pg.format", - ".ei6": "application/vnd.pg.osasli", - ".pil": "application/vnd.piaccess.application-license", - ".efif": "application/vnd.picsel", - ".wg": "application/vnd.pmi.widget", - ".plf": "application/vnd.pocketlearn", - ".pbd": "application/vnd.powerbuilder6", - ".preminet": "application/vnd.preminet", - ".box": "application/vnd.previewsystems.box", - ".vbox": "application/vnd.previewsystems.box", - ".mgz": "application/vnd.proteus.magazine", - ".qps": "application/vnd.publishare-delta-tree", - ".ptid": "application/vnd.pvi.ptid1", - ".bar": "application/vnd.qualcomm.brew-app-res", - ".qxd": "application/vnd.Quark.QuarkXPress", - ".qxt": "application/vnd.Quark.QuarkXPress", - ".qwd": "application/vnd.Quark.QuarkXPress", - ".qwt": "application/vnd.Quark.QuarkXPress", - ".qxl": "application/vnd.Quark.QuarkXPress", - ".qxb": "application/vnd.Quark.QuarkXPress", - ".quox": "application/vnd.quobject-quoxdocument", - ".quiz": "application/vnd.quobject-quoxdocument", - ".tree": "application/vnd.rainstor.data", - ".rar": "application/vnd.rar", - ".bed": "application/vnd.realvnc.bed", - ".mxl": "application/vnd.recordare.musicxml", - ".cryptonote": "application/vnd.rig.cryptonote", - ".link66": "application/vnd.route66.link66+xml", - ".st": "application/vnd.sailingtracker.track", - ".scd": "application/vnd.scribus", - ".sla": "application/vnd.scribus", - ".slaz": "application/vnd.scribus", - ".s3df": "application/vnd.sealed.3df", - ".scsf": "application/vnd.sealed.csf", - ".sdoc": "application/vnd.sealed.doc", - ".sdo": "application/vnd.sealed.doc", - ".s1w": "application/vnd.sealed.doc", - ".seml": "application/vnd.sealed.eml", - ".sem": "application/vnd.sealed.eml", - ".smht": "application/vnd.sealed.mht", - ".smh": "application/vnd.sealed.mht", - ".sppt": "application/vnd.sealed.ppt", - ".s1p": "application/vnd.sealed.ppt", - ".stif": "application/vnd.sealed.tiff", - ".sxls": "application/vnd.sealed.xls", - ".sxl": "application/vnd.sealed.xls", - ".s1e": "application/vnd.sealed.xls", - ".stml": "application/vnd.sealedmedia.softseal.html", - ".s1h": "application/vnd.sealedmedia.softseal.html", - ".spdf": "application/vnd.sealedmedia.softseal.pdf", - ".spd": "application/vnd.sealedmedia.softseal.pdf", - ".s1a": "application/vnd.sealedmedia.softseal.pdf", - ".see": "application/vnd.seemail", - ".sema": "application/vnd.sema", - ".semd": "application/vnd.semd", - ".semf": "application/vnd.semf", - ".ifm": "application/vnd.shana.informed.formdata", - ".itp": "application/vnd.shana.informed.formtemplate", - ".iif": "application/vnd.shana.informed.interchange", - ".ipk": "application/vnd.shana.informed.package", - ".twd": "application/vnd.SimTech-MindMapper", - ".twds": "application/vnd.SimTech-MindMapper", - ".mmf": "application/vnd.smaf", - ".notebook": "application/vnd.smart.notebook", - ".teacher": "application/vnd.smart.teacher", - ".fo": "application/vnd.software602.filler.form+xml", - ".zfo": "application/vnd.software602.filler.form-xml-zip", - ".sdkm": "application/vnd.solent.sdkm+xml", - ".sdkd": "application/vnd.solent.sdkm+xml", - ".dxp": "application/vnd.spotfire.dxp", - ".sfs": "application/vnd.spotfire.sfs", - ".smzip": "application/vnd.stepmania.package", - ".sm": "application/vnd.stepmania.stepchart", - ".wadl": "application/vnd.sun.wadl+xml", - ".sus": "application/vnd.sus-calendar", - ".susp": "application/vnd.sus-calendar", - ".xsm": "application/vnd.syncml+xml", - ".bdm": "application/vnd.syncml.dm+wbxml", - ".xdm": "application/vnd.syncml.dm+xml", - ".ddf": "application/vnd.syncml.dmddf+xml", - ".tao": "application/vnd.tao.intent-module-archive", - ".pcap": "application/vnd.tcpdump.pcap", - ".cap": "application/vnd.tcpdump.pcap", - ".dmp": "application/vnd.tcpdump.pcap", - ".qvd": "application/vnd.theqvd", - ".vfr": "application/vnd.tml", - ".viaframe": "application/vnd.tml", - ".tmo": "application/vnd.tmobile-livetv", - ".tpt": "application/vnd.trid.tpt", - ".mxs": "application/vnd.triscape.mxs", - ".tra": "application/vnd.trueapp", - ".ufdl": "application/vnd.ufdl", - ".ufd": "application/vnd.ufdl", - ".frm": "application/vnd.ufdl", - ".utz": "application/vnd.uiq.theme", - ".umj": "application/vnd.umajin", - ".unityweb": "application/vnd.unity", - ".uoml": "application/vnd.uoml+xml", - ".uo": "application/vnd.uoml+xml", - ".urim": "application/vnd.uri-map", - ".urimap": "application/vnd.uri-map", - ".vmt": "application/vnd.valve.source.material", - ".vcx": "application/vnd.vcx", - ".mxi": "application/vnd.vd-study", - ".study-inter": "application/vnd.vd-study", - ".model-inter": "application/vnd.vd-study", - ".vwx": "application/vnd.vectorworks", - ".vsc": "application/vnd.vidsoft.vidconference", - ".vsd": "application/vnd.visio", - ".vst": "application/vnd.visio", - ".vsw": "application/vnd.visio", - ".vss": "application/vnd.visio", - ".vis": "application/vnd.visionary", - ".vsf": "application/vnd.vsf", - ".sic": "application/vnd.wap.sic", - ".slc": "application/vnd.wap.slc", - ".wbxml": "application/vnd.wap.wbxml", - ".wmlc": "application/vnd.wap.wmlc", - ".wmlsc": "application/vnd.wap.wmlscriptc", - ".wtb": "application/vnd.webturbo", - ".p2p": "application/vnd.wfa.p2p", - ".wsc": "application/vnd.wfa.wsc", - ".wmc": "application/vnd.wmc", - ".m": "application/vnd.wolfram.mathematica.package", - ".nbp": "application/vnd.wolfram.player", - ".wpd": "application/vnd.wordperfect", - ".wqd": "application/vnd.wqd", - ".stf": "application/vnd.wt.stf", - ".wv": "application/vnd.wv.csp+wbxml", - ".xar": "application/vnd.xara", - ".xfdl": "application/vnd.xfdl", - ".xfd": "application/vnd.xfdl", - ".cpkg": "application/vnd.xmpie.cpkg", - ".dpkg": "application/vnd.xmpie.dpkg", - ".ppkg": "application/vnd.xmpie.ppkg", - ".xlim": "application/vnd.xmpie.xlim", - ".hvd": "application/vnd.yamaha.hv-dic", - ".hvs": "application/vnd.yamaha.hv-script", - ".hvp": "application/vnd.yamaha.hv-voice", - ".osf": "application/vnd.yamaha.openscoreformat", - ".saf": "application/vnd.yamaha.smaf-audio", - ".spf": "application/vnd.yamaha.smaf-phrase", - ".yme": "application/vnd.yaoweme", - ".cmp": "application/vnd.yellowriver-custom-menu", - ".zir": "application/vnd.zul", - ".zirz": "application/vnd.zul", - ".zaz": "application/vnd.zzazz.deck+xml", - ".vxml": "application/voicexml+xml", - ".wif": "application/watcherinfo+xml", - ".wgt": "application/widget", - ".wsdl": "application/wsdl+xml", - ".wspolicy": "application/wspolicy+xml", - ".xav": "application/xcap-att+xml", - ".xca": "application/xcap-caps+xml", - ".xdf": "application/xcap-diff+xml", - ".xel": "application/xcap-el+xml", - ".xer": "application/xcap-error+xml", - ".xns": "application/xcap-ns+xml", - ".xhtml": "application/xhtml+xml", - ".xhtm": "application/xhtml+xml", - ".xht": "application/xhtml+xml", - ".dtd": "application/xml-dtd", - ".xop": "application/xop+xml", - ".xsl": "application/xslt+xml", - ".xslt": "application/xslt+xml", - ".mxml": "application/xv+xml", - ".xhvml": "application/xv+xml", - ".xvml": "application/xv+xml", - ".xvm": "application/xv+xml", - ".yang": "application/yang", - ".yin": "application/yin+xml", - ".zip": "application/zip", - ".726": "audio/32kadpcm", - ".ac3": "audio/ac3", - ".amr": "audio/AMR", - ".awb": "audio/AMR-WB", - ".acn": "audio/asc", - ".aal": "audio/ATRAC-ADVANCED-LOSSLESS", - ".atx": "audio/ATRAC-X", - ".at3": "audio/ATRAC3", - ".aa3": "audio/ATRAC3", - ".omg": "audio/ATRAC3", - ".au": "audio/basic", - ".snd": "audio/basic", - ".dls": "audio/dls", - ".evc": "audio/EVRC", - ".evb": "audio/EVRCB", - ".enw": "audio/EVRCNW", - ".evw": "audio/EVRCWB", - ".lbc": "audio/iLBC", - ".l16": "audio/L16", - ".mxmf": "audio/mobile-xmf", - ".m4a": "audio/mp4", - ".mp3": "audio/mpeg", - ".mpga": "audio/mpeg", - ".mp1": "audio/mpeg", - ".mp2": "audio/mpeg", - ".oga": "audio/ogg", - ".ogg": "audio/ogg", - ".opus": "audio/ogg", - ".spx": "audio/ogg", - ".sid": "audio/prs.sid", - ".psid": "audio/prs.sid", - ".qcp": "audio/qcelp", - ".smv": "audio/SMV", - ".koz": "audio/vnd.audikoz", - ".uva": "audio/vnd.dece.audio", - ".uvva": "audio/vnd.dece.audio", - ".eol": "audio/vnd.digital-winds", - ".mlp": "audio/vnd.dolby.mlp", - ".dts": "audio/vnd.dts", - ".dtshd": "audio/vnd.dts.hd", - ".plj": "audio/vnd.everad.plj", - ".lvp": "audio/vnd.lucent.voice", - ".pya": "audio/vnd.ms-playready.media.pya", - ".vbk": "audio/vnd.nortel.vbk", - ".ecelp4800": "audio/vnd.nuera.ecelp4800", - ".ecelp7470": "audio/vnd.nuera.ecelp7470", - ".ecelp9600": "audio/vnd.nuera.ecelp9600", - ".rip": "audio/vnd.rip", - ".smp3": "audio/vnd.sealedmedia.softseal.mpeg", - ".smp": "audio/vnd.sealedmedia.softseal.mpeg", - ".s1m": "audio/vnd.sealedmedia.softseal.mpeg", - ".ttc": "font/collection", - ".otf": "font/otf", - ".ttf": "font/ttf", - ".woff": "font/woff", - ".woff2": "font/woff2", - ".bmp": "image/bmp", - ".dib": "image/bmp", - ".cgm": "image/cgm", - ".drle": "image/dicom-rle", - ".emf": "image/emf", - ".fits": "image/fits", - ".fit": "image/fits", - ".fts": "image/fits", - ".gif": "image/gif", - ".ief": "image/ief", - ".jls": "image/jls", - ".jp2": "image/jp2", - ".jpg2": "image/jp2", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".jpe": "image/jpeg", - ".jfif": "image/jpeg", - ".jpm": "image/jpm", - ".jpgm": "image/jpm", - ".jpx": "image/jpx", - ".jpf": "image/jpx", - ".ktx": "image/ktx", - ".png": "image/png", - ".btif": "image/prs.btif", - ".btf": "image/prs.btif", - ".pti": "image/prs.pti", - ".svg": "image/svg+xml", - ".svgz": "image/svg+xml", - ".t38": "image/t38", - ".tiff": "image/tiff", - ".tif": "image/tiff", - ".tfx": "image/tiff-fx", - ".psd": "image/vnd.adobe.photoshop", - ".azv": "image/vnd.airzip.accelerator.azv", - ".uvi": "image/vnd.dece.graphic", - ".uvvi": "image/vnd.dece.graphic", - ".uvg": "image/vnd.dece.graphic", - ".uvvg": "image/vnd.dece.graphic", - ".djvu": "image/vnd.djvu", - ".djv": "image/vnd.djvu", - ".dwg": "image/vnd.dwg", - ".dxf": "image/vnd.dxf", - ".fbs": "image/vnd.fastbidsheet", - ".fpx": "image/vnd.fpx", - ".fst": "image/vnd.fst", - ".mmr": "image/vnd.fujixerox.edmics-mmr", - ".rlc": "image/vnd.fujixerox.edmics-rlc", - ".pgb": "image/vnd.globalgraphics.pgb", - ".ico": "image/vnd.microsoft.icon", - ".apng": "image/vnd.mozilla.apng", - ".mdi": "image/vnd.ms-modi", - ".hdr": "image/vnd.radiance", - ".rgbe": "image/vnd.radiance", - ".xyze": "image/vnd.radiance", - ".spng": "image/vnd.sealed.png", - ".spn": "image/vnd.sealed.png", - ".s1n": "image/vnd.sealed.png", - ".sgif": "image/vnd.sealedmedia.softseal.gif", - ".sgi": "image/vnd.sealedmedia.softseal.gif", - ".s1g": "image/vnd.sealedmedia.softseal.gif", - ".sjpg": "image/vnd.sealedmedia.softseal.jpg", - ".sjp": "image/vnd.sealedmedia.softseal.jpg", - ".s1j": "image/vnd.sealedmedia.softseal.jpg", - ".tap": "image/vnd.tencent.tap", - ".vtf": "image/vnd.valve.source.texture", - ".wbmp": "image/vnd.wap.wbmp", - ".xif": "image/vnd.xiff", - ".pcx": "image/vnd.zbrush.pcx", - ".wmf": "image/wmf", - ".u8msg": "message/global", - ".u8dsn": "message/global-delivery-status", - ".u8mdn": "message/global-disposition-notification", - ".u8hdr": "message/global-headers", - ".eml": "message/rfc822", - ".mail": "message/rfc822", - ".art": "message/rfc822", - ".gltf": "model/gltf+json", - ".igs": "model/iges", - ".iges": "model/iges", - ".msh": "model/mesh", - ".mesh": "model/mesh", - ".silo": "model/mesh", - ".dae": "model/vnd.collada+xml", - ".dwf": "model/vnd.dwf", - ".gdl": "model/vnd.gdl", - ".gsm": "model/vnd.gdl", - ".win": "model/vnd.gdl", - ".dor": "model/vnd.gdl", - ".lmp": "model/vnd.gdl", - ".rsm": "model/vnd.gdl", - ".msm": "model/vnd.gdl", - ".ism": "model/vnd.gdl", - ".gtw": "model/vnd.gtw", - ".moml": "model/vnd.moml+xml", - ".mts": "model/vnd.mts", - ".ogex": "model/vnd.opengex", - ".x_b": "model/vnd.parasolid.transmit.binary", - ".xmt_bin": "model/vnd.parasolid.transmit.binary", - ".x_t": "model/vnd.parasolid.transmit.text", - ".xmt_txt": "model/vnd.parasolid.transmit.text", - ".bsp": "model/vnd.valve.source.compiled-map", - ".vtu": "model/vnd.vtu", - ".wrl": "model/vrml", - ".vrml": "model/vrml", - ".x3db": "model/x3d+xml", - ".x3dv": "model/x3d-vrml", - ".x3dvz": "model/x3d-vrml", - ".bmed": "multipart/vnd.bint.med-plus", - ".vpm": "multipart/voice-message", - ".appcache": "text/cache-manifest", - ".manifest": "text/cache-manifest", - ".ics": "text/calendar", - ".ifb": "text/calendar", - ".css": "text/css", - ".csv": "text/csv", - ".csvs": "text/csv-schema", - ".soa": "text/dns", - ".zone": "text/dns", - ".html": "text/html", - ".htm": "text/html", - ".cnd": "text/jcr-cnd", - ".markdown": "text/markdown", - ".md": "text/markdown", - ".miz": "text/mizar", - ".n3": "text/n3", - ".txt": "text/plain", - ".asc": "text/plain", - ".text": "text/plain", - ".pm": "text/plain", - ".el": "text/plain", - ".c": "text/plain", - ".h": "text/plain", - ".cc": "text/plain", - ".hh": "text/plain", - ".cxx": "text/plain", - ".hxx": "text/plain", - ".f90": "text/plain", - ".conf": "text/plain", - ".log": "text/plain", - ".provn": "text/provenance-notation", - ".rst": "text/prs.fallenstein.rst", - ".tag": "text/prs.lines.tag", - ".dsc": "text/prs.lines.tag", - ".rtx": "text/richtext", - ".sgml": "text/sgml", - ".sgm": "text/sgml", - ".tsv": "text/tab-separated-values", - ".t": "text/troff", - ".tr": "text/troff", - ".roff": "text/troff", - ".ttl": "text/turtle", - ".uris": "text/uri-list", - ".uri": "text/uri-list", - ".vcf": "text/vcard", - ".vcard": "text/vcard", - ".a": "text/vnd.a", - ".abc": "text/vnd.abc", - ".ascii": "text/vnd.ascii-art", - ".copyright": "text/vnd.debian.copyright", - ".dms": "text/vnd.DMClientScript", - ".sub": "text/vnd.dvb.subtitle", - ".jtd": "text/vnd.esmertec.theme-descriptor", - ".fly": "text/vnd.fly", - ".flx": "text/vnd.fmi.flexstor", - ".gv": "text/vnd.graphviz", - ".dot": "text/vnd.graphviz", - ".3dml": "text/vnd.in3d.3dml", - ".3dm": "text/vnd.in3d.3dml", - ".spot": "text/vnd.in3d.spot", - ".spo": "text/vnd.in3d.spot", - ".mpf": "text/vnd.ms-mediapackage", - ".ccc": "text/vnd.net2phone.commcenter.command", - ".uric": "text/vnd.si.uricatalogue", - ".jad": "text/vnd.sun.j2me.app-descriptor", - ".ts": "text/vnd.trolltech.linguist", - ".si": "text/vnd.wap.si", - ".sl": "text/vnd.wap.sl", - ".wml": "text/vnd.wap.wml", - ".wmls": "text/vnd.wap.wmlscript", - ".xml": "text/xml", - ".xsd": "text/xml", - ".rng": "text/xml", - ".ent": "text/xml-external-parsed-entity", - ".3gp": "video/3gpp", - ".3gpp": "video/3gpp", - ".3g2": "video/3gpp2", - ".3gpp2": "video/3gpp2", - ".m4s": "video/iso.segment", - ".mj2": "video/mj2", - ".mjp2": "video/mj2", - ".mp4": "video/mp4", - ".mpg4": "video/mp4", - ".m4v": "video/mp4", - ".mpeg": "video/mpeg", - ".mpg": "video/mpeg", - ".mpe": "video/mpeg", - ".m1v": "video/mpeg", - ".m2v": "video/mpeg", - ".ogv": "video/ogg", - ".mov": "video/quicktime", - ".qt": "video/quicktime", - ".uvh": "video/vnd.dece.hd", - ".uvvh": "video/vnd.dece.hd", - ".uvm": "video/vnd.dece.mobile", - ".uvvm": "video/vnd.dece.mobile", - ".uvu": "video/vnd.dece.mp4", - ".uvvu": "video/vnd.dece.mp4", - ".uvp": "video/vnd.dece.pd", - ".uvvp": "video/vnd.dece.pd", - ".uvs": "video/vnd.dece.sd", - ".uvvs": "video/vnd.dece.sd", - ".uvv": "video/vnd.dece.video", - ".uvvv": "video/vnd.dece.video", - ".dvb": "video/vnd.dvb.file", - ".fvt": "video/vnd.fvt", - ".mxu": "video/vnd.mpegurl", - ".m4u": "video/vnd.mpegurl", - ".pyv": "video/vnd.ms-playready.media.pyv", - ".nim": "video/vnd.nokia.interleaved-multimedia", - ".bik": "video/vnd.radgamettools.bink", - ".bk2": "video/vnd.radgamettools.bink", - ".smk": "video/vnd.radgamettools.smacker", - ".smpg": "video/vnd.sealed.mpeg1", - ".s11": "video/vnd.sealed.mpeg1", - ".s14": "video/vnd.sealed.mpeg4", - ".sswf": "video/vnd.sealed.swf", - ".ssw": "video/vnd.sealed.swf", - ".smov": "video/vnd.sealedmedia.softseal.mov", - ".smo": "video/vnd.sealedmedia.softseal.mov", - ".s1q": "video/vnd.sealedmedia.softseal.mov", - ".viv": "video/vnd.vivo", - ".cpt": "application/mac-compactpro", - ".metalink": "application/metalink+xml", - ".owx": "application/owl+xml", - ".rss": "application/rss+xml", - ".apk": "application/vnd.android.package-archive", - ".dd": "application/vnd.oma.dd+xml", - ".dcf": "application/vnd.oma.drm.content", - ".o4a": "application/vnd.oma.drm.dcf", - ".o4v": "application/vnd.oma.drm.dcf", - ".dm": "application/vnd.oma.drm.message", - ".drc": "application/vnd.oma.drm.rights+wbxml", - ".dr": "application/vnd.oma.drm.rights+xml", - ".sxc": "application/vnd.sun.xml.calc", - ".stc": "application/vnd.sun.xml.calc.template", - ".sxd": "application/vnd.sun.xml.draw", - ".std": "application/vnd.sun.xml.draw.template", - ".sxi": "application/vnd.sun.xml.impress", - ".sti": "application/vnd.sun.xml.impress.template", - ".sxm": "application/vnd.sun.xml.math", - ".sxw": "application/vnd.sun.xml.writer", - ".sxg": "application/vnd.sun.xml.writer.global", - ".stw": "application/vnd.sun.xml.writer.template", - ".sis": "application/vnd.symbian.install", - ".mms": "application/vnd.wap.mms-message", - ".anx": "application/x-annodex", - ".bcpio": "application/x-bcpio", - ".torrent": "application/x-bittorrent", - ".bz2": "application/x-bzip2", - ".vcd": "application/x-cdlink", - ".crx": "application/x-chrome-extension", - ".cpio": "application/x-cpio", - ".csh": "application/x-csh", - ".dcr": "application/x-director", - ".dir": "application/x-director", - ".dxr": "application/x-director", - ".dvi": "application/x-dvi", - ".spl": "application/x-futuresplash", - ".gtar": "application/x-gtar", - ".hdf": "application/x-hdf", - ".jar": "application/x-java-archive", - ".jnlp": "application/x-java-jnlp-file", - ".pack": "application/x-java-pack200", - ".kil": "application/x-killustrator", - ".latex": "application/x-latex", - ".nc": "application/x-netcdf", - ".cdf": "application/x-netcdf", - ".pl": "application/x-perl", - ".rpm": "application/x-rpm", - ".sh": "application/x-sh", - ".shar": "application/x-shar", - ".sit": "application/x-stuffit", - ".sv4cpio": "application/x-sv4cpio", - ".sv4crc": "application/x-sv4crc", - ".tar": "application/x-tar", - ".tcl": "application/x-tcl", - ".tex": "application/x-tex", - ".texinfo": "application/x-texinfo", - ".texi": "application/x-texinfo", - ".man": "application/x-troff-man", - ".1": "application/x-troff-man", - ".2": "application/x-troff-man", - ".3": "application/x-troff-man", - ".4": "application/x-troff-man", - ".5": "application/x-troff-man", - ".6": "application/x-troff-man", - ".7": "application/x-troff-man", - ".8": "application/x-troff-man", - ".me": "application/x-troff-me", - ".ms": "application/x-troff-ms", - ".ustar": "application/x-ustar", - ".src": "application/x-wais-source", - ".xpi": "application/x-xpinstall", - ".xspf": "application/x-xspf+xml", - ".xz": "application/x-xz", - ".mid": "audio/midi", - ".midi": "audio/midi", - ".kar": "audio/midi", - ".aif": "audio/x-aiff", - ".aiff": "audio/x-aiff", - ".aifc": "audio/x-aiff", - ".axa": "audio/x-annodex", - ".flac": "audio/x-flac", - ".mka": "audio/x-matroska", - ".mod": "audio/x-mod", - ".ult": "audio/x-mod", - ".uni": "audio/x-mod", - ".m15": "audio/x-mod", - ".mtm": "audio/x-mod", - ".669": "audio/x-mod", - ".med": "audio/x-mod", - ".m3u": "audio/x-mpegurl", - ".wax": "audio/x-ms-wax", - ".wma": "audio/x-ms-wma", - ".ram": "audio/x-pn-realaudio", - ".rm": "audio/x-pn-realaudio", - ".ra": "audio/x-realaudio", - ".s3m": "audio/x-s3m", - ".stm": "audio/x-stm", - ".wav": "audio/x-wav", - ".xyz": "chemical/x-xyz", - ".webp": "image/webp", - ".ras": "image/x-cmu-raster", - ".pnm": "image/x-portable-anymap", - ".pbm": "image/x-portable-bitmap", - ".pgm": "image/x-portable-graymap", - ".ppm": "image/x-portable-pixmap", - ".rgb": "image/x-rgb", - ".tga": "image/x-targa", - ".xbm": "image/x-xbitmap", - ".xpm": "image/x-xpixmap", - ".xwd": "image/x-xwindowdump", - ".sandboxed": "text/html-sandboxed", - ".pod": "text/x-pod", - ".etx": "text/x-setext", - ".webm": "video/webm", - ".axv": "video/x-annodex", - ".flv": "video/x-flv", - ".fxm": "video/x-javafx", - ".mkv": "video/x-matroska", - ".mk3d": "video/x-matroska-3d", - ".asx": "video/x-ms-asf", - ".wm": "video/x-ms-wm", - ".wmv": "video/x-ms-wmv", - ".wmx": "video/x-ms-wmx", - ".wvx": "video/x-ms-wvx", - ".avi": "video/x-msvideo", - ".movie": "video/x-sgi-movie", - ".ice": "x-conference/x-cooltalk", - ".sisx": "x-epoc/x-sisx-app", - } - for ext, name := range mimeTypes { - if err := mime.AddExtensionType(ext, name); err != nil { - panic(err) - } - } -} diff --git a/swarm/api/http/middleware.go b/swarm/api/http/middleware.go deleted file mode 100644 index e6e263f4c..000000000 --- a/swarm/api/http/middleware.go +++ /dev/null @@ -1,162 +0,0 @@ -package http - -import ( - "fmt" - "net/http" - "runtime/debug" - "strings" - "time" - - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/sctx" - "github.com/ethereum/go-ethereum/swarm/spancontext" - "github.com/pborman/uuid" -) - -// Adapt chains h (main request handler) main handler to adapters (middleware handlers) -// Please note that the order of execution for `adapters` is FIFO (adapters[0] will be executed first) -func Adapt(h http.Handler, adapters ...Adapter) http.Handler { - for i := range adapters { - adapter := adapters[len(adapters)-1-i] - h = adapter(h) - } - return h -} - -type Adapter func(http.Handler) http.Handler - -func SetRequestID(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(SetRUID(r.Context(), uuid.New()[:8])) - metrics.GetOrRegisterCounter(fmt.Sprintf("http.request.%s", r.Method), nil).Inc(1) - log.Info("created ruid for request", "ruid", GetRUID(r.Context()), "method", r.Method, "url", r.RequestURI) - - h.ServeHTTP(w, r) - }) -} - -func SetRequestHost(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(sctx.SetHost(r.Context(), r.Host)) - log.Info("setting request host", "ruid", GetRUID(r.Context()), "host", sctx.GetHost(r.Context())) - - h.ServeHTTP(w, r) - }) -} - -func ParseURI(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - respondError(w, r, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest) - return - } - if uri.Addr != "" && strings.HasPrefix(uri.Addr, "0x") { - uri.Addr = strings.TrimPrefix(uri.Addr, "0x") - - msg := fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.<br/> - Please click <a href='%[1]s'>here</a> if your browser does not redirect you within 5 seconds.<script>setTimeout("location.href='%[1]s';",5000);</script>`, "/"+uri.String()) - w.WriteHeader(http.StatusNotFound) - w.Write([]byte(msg)) - return - } - - ctx := r.Context() - r = r.WithContext(SetURI(ctx, uri)) - log.Debug("parsed request path", "ruid", GetRUID(r.Context()), "method", r.Method, "uri.Addr", uri.Addr, "uri.Path", uri.Path, "uri.Scheme", uri.Scheme) - - h.ServeHTTP(w, r) - }) -} - -func InitLoggingResponseWriter(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tn := time.Now() - - writer := newLoggingResponseWriter(w) - h.ServeHTTP(writer, r) - - ts := time.Since(tn) - log.Info("request served", "ruid", GetRUID(r.Context()), "code", writer.statusCode, "time", ts) - metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.time", r.Method), nil).Update(ts) - metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.%d.time", r.Method, writer.statusCode), nil).Update(ts) - }) -} - -// InitUploadTag creates a new tag for an upload to the local HTTP proxy -// if a tag is not named using the SwarmTagHeaderName, a fallback name will be used -// when the Content-Length header is set, an ETA on chunking will be available since the -// number of chunks to be split is known in advance (not including enclosing manifest chunks) -// the tag can later be accessed using the appropriate identifier in the request context -func InitUploadTag(h http.Handler, tags *chunk.Tags) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ( - tagName string - err error - estimatedTotal int64 = 0 - contentType = r.Header.Get("Content-Type") - headerTag = r.Header.Get(SwarmTagHeaderName) - ) - if headerTag != "" { - tagName = headerTag - log.Trace("got tag name from http header", "tagName", tagName) - } else { - tagName = fmt.Sprintf("unnamed_tag_%d", time.Now().Unix()) - } - - if !strings.Contains(contentType, "multipart") && r.ContentLength > 0 { - log.Trace("calculating tag size", "contentType", contentType, "contentLength", r.ContentLength) - uri := GetURI(r.Context()) - if uri != nil { - log.Debug("got uri from context") - if uri.Addr == "encrypt" { - estimatedTotal = calculateNumberOfChunks(r.ContentLength, true) - } else { - estimatedTotal = calculateNumberOfChunks(r.ContentLength, false) - } - } - } - - log.Trace("creating tag", "tagName", tagName, "estimatedTotal", estimatedTotal) - - t, err := tags.New(tagName, estimatedTotal) - if err != nil { - log.Error("error creating tag", "err", err, "tagName", tagName) - } - - log.Trace("setting tag id to context", "uid", t.Uid) - ctx := sctx.SetTag(r.Context(), t.Uid) - - h.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func InstrumentOpenTracing(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - uri := GetURI(r.Context()) - if uri == nil || r.Method == "" || (uri != nil && uri.Scheme == "") { - h.ServeHTTP(w, r) // soft fail - return - } - spanName := fmt.Sprintf("http.%s.%s", r.Method, uri.Scheme) - ctx, sp := spancontext.StartSpan(r.Context(), spanName) - - defer sp.Finish() - h.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func RecoverPanic(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer func() { - if err := recover(); err != nil { - log.Error("panic recovery!", "stack trace", string(debug.Stack()), "url", r.URL.String(), "headers", r.Header) - } - }() - h.ServeHTTP(w, r) - }) -} diff --git a/swarm/api/http/response.go b/swarm/api/http/response.go deleted file mode 100644 index c851a3992..000000000 --- a/swarm/api/http/response.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "encoding/json" - "fmt" - "html/template" - "net/http" - "strings" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/api" -) - -var ( - htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil) - jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil) - plaintextCounter = metrics.NewRegisteredCounter("api.http.errorpage.plaintext.count", nil) -) - -type ResponseParams struct { - Msg template.HTML - Code int - Timestamp string - template *template.Template - Details template.HTML -} - -// ShowMultipleChoices is used when a user requests a resource in a manifest which results -// in ambiguous results. It returns a HTML page with clickable links of each of the entry -// in the manifest which fits the request URI ambiguity. -// For example, if the user requests bzz:/<hash>/read and that manifest contains entries -// "readme.md" and "readinglist.txt", a HTML page is returned with this two links. -// This only applies if the manifest has no default entry -func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) { - log.Debug("ShowMultipleChoices", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context())) - msg := "" - if list.Entries == nil { - respondError(w, r, "Could not resolve", http.StatusInternalServerError) - return - } - requestUri := strings.TrimPrefix(r.RequestURI, "/") - - uri, err := api.Parse(requestUri) - if err != nil { - respondError(w, r, "Bad Request", http.StatusBadRequest) - } - - uri.Scheme = "bzz-list" - msg += fmt.Sprintf("Disambiguation:<br/>Your request may refer to multiple choices.<br/>Click <a class=\"orange\" href='"+"/"+uri.String()+"'>here</a> if your browser does not redirect you within 5 seconds.<script>setTimeout(\"location.href='%s';\",5000);</script><br/>", "/"+uri.String()) - respondTemplate(w, r, "error", msg, http.StatusMultipleChoices) -} - -func respondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg string, code int) { - log.Debug("respondTemplate", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context())) - respond(w, r, &ResponseParams{ - Code: code, - Msg: template.HTML(msg), - Timestamp: time.Now().Format(time.RFC1123), - template: TemplatesMap[templateName], - }) -} - -func respondError(w http.ResponseWriter, r *http.Request, msg string, code int) { - log.Info("respondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()), "code", code, "msg", msg) - respondTemplate(w, r, "error", msg, code) -} - -func respond(w http.ResponseWriter, r *http.Request, params *ResponseParams) { - w.WriteHeader(params.Code) - - if params.Code >= 400 { - w.Header().Del("Cache-Control") - w.Header().Del("ETag") - } - - acceptHeader := r.Header.Get("Accept") - // this cannot be in a switch since an Accept header can have multiple values: "Accept: */*, text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8" - if strings.Contains(acceptHeader, "application/json") { - if err := respondJSON(w, r, params); err != nil { - respondError(w, r, "Internal server error", http.StatusInternalServerError) - } - } else if strings.Contains(acceptHeader, "text/html") { - respondHTML(w, r, params) - } else { - respondPlaintext(w, r, params) //returns nice errors for curl - } -} - -func respondHTML(w http.ResponseWriter, r *http.Request, params *ResponseParams) { - htmlCounter.Inc(1) - log.Info("respondHTML", "ruid", GetRUID(r.Context()), "code", params.Code) - err := params.template.Execute(w, params) - if err != nil { - log.Error(err.Error()) - } -} - -func respondJSON(w http.ResponseWriter, r *http.Request, params *ResponseParams) error { - jsonCounter.Inc(1) - log.Info("respondJSON", "ruid", GetRUID(r.Context()), "code", params.Code) - w.Header().Set("Content-Type", "application/json") - return json.NewEncoder(w).Encode(params) -} - -func respondPlaintext(w http.ResponseWriter, r *http.Request, params *ResponseParams) error { - plaintextCounter.Inc(1) - log.Info("respondPlaintext", "ruid", GetRUID(r.Context()), "code", params.Code) - w.Header().Set("Content-Type", "text/plain") - strToWrite := "Code: " + fmt.Sprintf("%d", params.Code) + "\n" - strToWrite += "Message: " + string(params.Msg) + "\n" - strToWrite += "Timestamp: " + params.Timestamp + "\n" - _, err := w.Write([]byte(strToWrite)) - return err -} diff --git a/swarm/api/http/response_test.go b/swarm/api/http/response_test.go deleted file mode 100644 index 486c19ab0..000000000 --- a/swarm/api/http/response_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "strings" - "testing" - - "golang.org/x/net/html" -) - -func TestError(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/this_should_fail_as_no_bzz_protocol_present" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 && !strings.Contains(string(respbody), "Invalid URI "/this_should_fail_as_no_bzz_protocol_present": unknown scheme") { - t.Fatalf("Response body does not match, expected: %v, to contain: %v; received code %d, expected code: %d", string(respbody), "Invalid bzz URI: unknown scheme", 400, resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func Test404Page(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/1234567890123456789012345678901234567890123456789012345678901234" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 || !strings.Contains(string(respbody), "404") { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func Test500Page(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/thisShouldFailWith500Code" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} -func Test500PageWith0xHashPrefix(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/0xthisShouldFailWith500CodeAndAHelpfulMessage" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - if !strings.Contains(string(respbody), "The requested hash seems to be prefixed with") { - t.Fatalf("Did not receive the expected error message") - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func TestJsonResponse(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/thisShouldFailWith500Code/" - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - req.Header.Set("Accept", "application/json") - resp, err = http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - if !isJSON(string(respbody)) { - t.Fatalf("Expected response to be JSON, received invalid JSON: %s", string(respbody)) - } - -} - -func isJSON(s string) bool { - var js map[string]interface{} - return json.Unmarshal([]byte(s), &js) == nil -} diff --git a/swarm/api/http/roundtripper.go b/swarm/api/http/roundtripper.go deleted file mode 100644 index be8ea3985..000000000 --- a/swarm/api/http/roundtripper.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "fmt" - "net/http" - - "github.com/ethereum/go-ethereum/swarm/log" -) - -/* -http roundtripper to register for bzz url scheme -see https://github.com/ethereum/go-ethereum/issues/2040 -Usage: - -import ( - "github.com/ethereum/go-ethereum/common/httpclient" - "github.com/ethereum/go-ethereum/swarm/api/http" -) -client := httpclient.New() -// for (private) swarm proxy running locally -client.RegisterScheme("bzz", &http.RoundTripper{Port: port}) -client.RegisterScheme("bzz-immutable", &http.RoundTripper{Port: port}) -client.RegisterScheme("bzz-raw", &http.RoundTripper{Port: port}) - -The port you give the Roundtripper is the port the swarm proxy is listening on. -If Host is left empty, localhost is assumed. - -Using a public gateway, the above few lines gives you the leanest -bzz-scheme aware read-only http client. You really only ever need this -if you need go-native swarm access to bzz addresses. -*/ - -type RoundTripper struct { - Host string - Port string -} - -func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { - host := self.Host - if len(host) == 0 { - host = "localhost" - } - url := fmt.Sprintf("http://%s:%s/%s:/%s/%s", host, self.Port, req.Proto, req.URL.Host, req.URL.Path) - log.Info(fmt.Sprintf("roundtripper: proxying request '%s' to '%s'", req.RequestURI, url)) - reqProxy, err := http.NewRequest(req.Method, url, req.Body) - if err != nil { - return nil, err - } - return http.DefaultClient.Do(reqProxy) -} diff --git a/swarm/api/http/roundtripper_test.go b/swarm/api/http/roundtripper_test.go deleted file mode 100644 index f99c4f35e..000000000 --- a/swarm/api/http/roundtripper_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "io/ioutil" - "net" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestRoundTripper(t *testing.T) { - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - w.Header().Set("Content-Type", "text/plain") - http.ServeContent(w, r, "", time.Unix(0, 0), strings.NewReader(r.RequestURI)) - } else { - http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed) - } - }) - - srv := httptest.NewServer(serveMux) - defer srv.Close() - - host, port, _ := net.SplitHostPort(srv.Listener.Addr().String()) - rt := &RoundTripper{Host: host, Port: port} - trans := &http.Transport{} - trans.RegisterProtocol("bzz", rt) - client := &http.Client{Transport: trans} - resp, err := client.Get("bzz://test.com/path") - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - - defer func() { - if resp != nil { - resp.Body.Close() - } - }() - - content, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - if string(content) != "/HTTP/1.1:/test.com/path" { - t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/HTTP/1.1:/test.com/path", string(content)) - } - -} diff --git a/swarm/api/http/sctx.go b/swarm/api/http/sctx.go deleted file mode 100644 index b8dafab0b..000000000 --- a/swarm/api/http/sctx.go +++ /dev/null @@ -1,34 +0,0 @@ -package http - -import ( - "context" - - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/sctx" -) - -type uriKey struct{} - -func GetRUID(ctx context.Context) string { - v, ok := ctx.Value(sctx.HTTPRequestIDKey{}).(string) - if ok { - return v - } - return "xxxxxxxx" -} - -func SetRUID(ctx context.Context, ruid string) context.Context { - return context.WithValue(ctx, sctx.HTTPRequestIDKey{}, ruid) -} - -func GetURI(ctx context.Context) *api.URI { - v, ok := ctx.Value(uriKey{}).(*api.URI) - if ok { - return v - } - return nil -} - -func SetURI(ctx context.Context, uri *api.URI) context.Context { - return context.WithValue(ctx, uriKey{}, uri) -} diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go deleted file mode 100644 index 88f2e4db9..000000000 --- a/swarm/api/http/server.go +++ /dev/null @@ -1,937 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -/* -A simple http server interface to Swarm -*/ -package http - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "math" - "mime" - "mime/multipart" - "net/http" - "os" - "path" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/sctx" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/rs/cors" -) - -var ( - postRawCount = metrics.NewRegisteredCounter("api.http.post.raw.count", nil) - postRawFail = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil) - postFilesCount = metrics.NewRegisteredCounter("api.http.post.files.count", nil) - postFilesFail = metrics.NewRegisteredCounter("api.http.post.files.fail", nil) - deleteCount = metrics.NewRegisteredCounter("api.http.delete.count", nil) - deleteFail = metrics.NewRegisteredCounter("api.http.delete.fail", nil) - getCount = metrics.NewRegisteredCounter("api.http.get.count", nil) - getFail = metrics.NewRegisteredCounter("api.http.get.fail", nil) - getFileCount = metrics.NewRegisteredCounter("api.http.get.file.count", nil) - getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil) - getFileFail = metrics.NewRegisteredCounter("api.http.get.file.fail", nil) - getListCount = metrics.NewRegisteredCounter("api.http.get.list.count", nil) - getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil) -) - -const SwarmTagHeaderName = "x-swarm-tag" - -type methodHandler map[string]http.Handler - -func (m methodHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - v, ok := m[r.Method] - if ok { - v.ServeHTTP(rw, r) - return - } - rw.WriteHeader(http.StatusMethodNotAllowed) -} - -func NewServer(api *api.API, corsString string) *Server { - var allowedOrigins []string - for _, domain := range strings.Split(corsString, ",") { - allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain)) - } - c := cors.New(cors.Options{ - AllowedOrigins: allowedOrigins, - AllowedMethods: []string{http.MethodPost, http.MethodGet, http.MethodDelete, http.MethodPatch, http.MethodPut}, - MaxAge: 600, - AllowedHeaders: []string{"*"}, - }) - - server := &Server{api: api} - - defaultMiddlewares := []Adapter{ - RecoverPanic, - SetRequestID, - SetRequestHost, - InitLoggingResponseWriter, - ParseURI, - InstrumentOpenTracing, - } - - tagAdapter := Adapter(func(h http.Handler) http.Handler { - return InitUploadTag(h, api.Tags) - }) - - defaultPostMiddlewares := append(defaultMiddlewares, tagAdapter) - - mux := http.NewServeMux() - mux.Handle("/bzz:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleBzzGet), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostFiles), - defaultPostMiddlewares..., - ), - "DELETE": Adapt( - http.HandlerFunc(server.HandleDelete), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-raw:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGet), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostRaw), - defaultPostMiddlewares..., - ), - }) - mux.Handle("/bzz-immutable:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleBzzGet), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-hash:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGet), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-list:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGetList), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-feed:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGetFeed), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostFeed), - defaultMiddlewares..., - ), - }) - - mux.Handle("/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleRootPaths), - SetRequestID, - InitLoggingResponseWriter, - ), - }) - server.Handler = c.Handler(mux) - - return server -} - -func (s *Server) ListenAndServe(addr string) error { - s.listenAddr = addr - return http.ListenAndServe(addr, s) -} - -// browser API for registering bzz url scheme handlers: -// https://developer.mozilla.org/en/docs/Web-based_protocol_handlers -// electron (chromium) api for registering bzz url scheme handlers: -// https://github.com/atom/electron/blob/master/docs/api/protocol.md -type Server struct { - http.Handler - api *api.API - listenAddr string -} - -func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) { - log.Debug("handleBzzGet", "ruid", GetRUID(r.Context()), "uri", r.RequestURI) - if r.Header.Get("Accept") == "application/x-tar" { - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - reader, err := s.api.GetDirectoryTar(r.Context(), s.api.Decryptor(r.Context(), credentials), uri) - if err != nil { - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", uri.Address().String())) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError) - return - } - defer reader.Close() - - w.Header().Set("Content-Type", "application/x-tar") - - fileName := uri.Addr - if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { - fileName = found - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s.tar\"", fileName)) - - w.WriteHeader(http.StatusOK) - io.Copy(w, reader) - return - } - - s.HandleGetFile(w, r) -} - -func (s *Server) HandleRootPaths(w http.ResponseWriter, r *http.Request) { - switch r.RequestURI { - case "/": - respondTemplate(w, r, "landing-page", "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", 200) - return - case "/robots.txt": - w.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat)) - fmt.Fprintf(w, "User-agent: *\nDisallow: /") - case "/favicon.ico": - w.WriteHeader(http.StatusOK) - w.Write(faviconBytes) - default: - respondError(w, r, "Not Found", http.StatusNotFound) - } -} - -// HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request -// body in swarm and returns the resulting storage address as a text/plain response -func (s *Server) HandlePostRaw(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - log.Debug("handle.post.raw", "ruid", ruid) - - tagUid := sctx.GetTag(r.Context()) - tag, err := s.api.Tags.Get(tagUid) - if err != nil { - log.Error("handle post raw got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err) - } - - postRawCount.Inc(1) - - toEncrypt := false - uri := GetURI(r.Context()) - if uri.Addr == "encrypt" { - toEncrypt = true - } - - if uri.Path != "" { - postRawFail.Inc(1) - respondError(w, r, "raw POST request cannot contain a path", http.StatusBadRequest) - return - } - - if uri.Addr != "" && uri.Addr != "encrypt" { - postRawFail.Inc(1) - respondError(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest) - return - } - - if r.Header.Get("Content-Length") == "" { - postRawFail.Inc(1) - respondError(w, r, "missing Content-Length header in request", http.StatusBadRequest) - return - } - - addr, wait, err := s.api.Store(r.Context(), r.Body, r.ContentLength, toEncrypt) - if err != nil { - postRawFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - wait(r.Context()) - tag.DoneSplit(addr) - - log.Debug("stored content", "ruid", ruid, "key", addr) - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, addr) -} - -// HandlePostFiles handles a POST request to -// bzz:/<hash>/<path> which contains either a single file or multiple files -// (either a tar archive or multipart form), adds those files either to an -// existing manifest or to a new manifest under <path> and returns the -// resulting manifest hash as a text/plain response -func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - log.Debug("handle.post.files", "ruid", ruid) - postFilesCount.Inc(1) - - contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, err.Error(), http.StatusBadRequest) - return - } - - toEncrypt := false - uri := GetURI(r.Context()) - if uri.Addr == "encrypt" { - toEncrypt = true - } - - var addr storage.Address - if uri.Addr != "" && uri.Addr != "encrypt" { - addr, err = s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError) - return - } - log.Debug("resolved key", "ruid", ruid, "key", addr) - } else { - addr, err = s.api.NewManifest(r.Context(), toEncrypt) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - log.Debug("new manifest", "ruid", ruid, "key", addr) - } - newAddr, err := s.api.UpdateManifest(r.Context(), addr, func(mw *api.ManifestWriter) error { - switch contentType { - case "application/x-tar": - _, err := s.handleTarUpload(r, mw) - if err != nil { - respondError(w, r, fmt.Sprintf("error uploading tarball: %v", err), http.StatusInternalServerError) - return err - } - return nil - case "multipart/form-data": - return s.handleMultipartUpload(r, params["boundary"], mw) - - default: - return s.handleDirectUpload(r, mw) - } - }) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError) - return - } - - tagUid := sctx.GetTag(r.Context()) - tag, err := s.api.Tags.Get(tagUid) - if err != nil { - log.Error("got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err) - } - - log.Debug("done splitting, setting tag total", "SPLIT", tag.Get(chunk.StateSplit), "TOTAL", tag.Total()) - tag.DoneSplit(newAddr) - - log.Debug("stored content", "ruid", ruid, "key", newAddr) - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, newAddr) -} - -func (s *Server) handleTarUpload(r *http.Request, mw *api.ManifestWriter) (storage.Address, error) { - log.Debug("handle.tar.upload", "ruid", GetRUID(r.Context()), "tag", sctx.GetTag(r.Context())) - - defaultPath := r.URL.Query().Get("defaultpath") - - key, err := s.api.UploadTar(r.Context(), r.Body, GetURI(r.Context()).Path, defaultPath, mw) - if err != nil { - return nil, err - } - return key, nil -} - -func (s *Server) handleMultipartUpload(r *http.Request, boundary string, mw *api.ManifestWriter) error { - ruid := GetRUID(r.Context()) - log.Debug("handle.multipart.upload", "ruid", ruid) - mr := multipart.NewReader(r.Body, boundary) - for { - part, err := mr.NextPart() - if err == io.EOF { - return nil - } else if err != nil { - return fmt.Errorf("error reading multipart form: %s", err) - } - - var size int64 - var reader io.Reader - if contentLength := part.Header.Get("Content-Length"); contentLength != "" { - size, err = strconv.ParseInt(contentLength, 10, 64) - if err != nil { - return fmt.Errorf("error parsing multipart content length: %s", err) - } - reader = part - } else { - // copy the part to a tmp file to get its size - tmp, err := ioutil.TempFile("", "swarm-multipart") - if err != nil { - return err - } - defer os.Remove(tmp.Name()) - defer tmp.Close() - size, err = io.Copy(tmp, part) - if err != nil { - return fmt.Errorf("error copying multipart content: %s", err) - } - if _, err := tmp.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("error copying multipart content: %s", err) - } - reader = tmp - } - - // add the entry under the path from the request - name := part.FileName() - if name == "" { - name = part.FormName() - } - uri := GetURI(r.Context()) - path := path.Join(uri.Path, name) - entry := &api.ManifestEntry{ - Path: path, - ContentType: part.Header.Get("Content-Type"), - Size: size, - } - log.Debug("adding path to new manifest", "ruid", ruid, "bytes", entry.Size, "path", entry.Path) - contentKey, err := mw.AddEntry(r.Context(), reader, entry) - if err != nil { - return fmt.Errorf("error adding manifest entry from multipart form: %s", err) - } - log.Debug("stored content", "ruid", ruid, "key", contentKey) - } -} - -func (s *Server) handleDirectUpload(r *http.Request, mw *api.ManifestWriter) error { - ruid := GetRUID(r.Context()) - log.Debug("handle.direct.upload", "ruid", ruid) - key, err := mw.AddEntry(r.Context(), r.Body, &api.ManifestEntry{ - Path: GetURI(r.Context()).Path, - ContentType: r.Header.Get("Content-Type"), - Mode: 0644, - Size: r.ContentLength, - }) - if err != nil { - return err - } - log.Debug("stored content", "ruid", ruid, "key", key) - return nil -} - -// HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes -// <path> from <manifest> and returns the resulting manifest hash as a -// text/plain response -func (s *Server) HandleDelete(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.delete", "ruid", ruid) - deleteCount.Inc(1) - newKey, err := s.api.Delete(r.Context(), uri.Addr, uri.Path) - if err != nil { - deleteFail.Inc(1) - respondError(w, r, fmt.Sprintf("could not delete from manifest: %v", err), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, newKey) -} - -// Handles feed manifest creation and feed updates -// The POST request admits a JSON structure as defined in the feeds package: `feed.updateRequestJSON` -// The requests can be to a) create a feed manifest, b) update a feed or c) both a+b: create a feed manifest and publish a first update -func (s *Server) HandlePostFeed(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.post.feed", "ruid", ruid) - var err error - - // Creation and update must send feed.updateRequestJSON JSON structure - body, err := ioutil.ReadAll(r.Body) - if err != nil { - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query()) - if err != nil { // couldn't parse query string or retrieve manifest - getFail.Inc(1) - httpStatus := http.StatusBadRequest - if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI { - httpStatus = http.StatusNotFound - } - respondError(w, r, fmt.Sprintf("cannot retrieve feed from manifest: %s", err), httpStatus) - return - } - - var updateRequest feed.Request - updateRequest.Feed = *fd - query := r.URL.Query() - - if err := updateRequest.FromValues(query, body); err != nil { // decodes request from query parameters - respondError(w, r, err.Error(), http.StatusBadRequest) - return - } - - switch { - case updateRequest.IsUpdate(): - // Verify that the signature is intact and that the signer is authorized - // to update this feed - // Check this early, to avoid creating a feed and then not being able to set its first update. - if err = updateRequest.Verify(); err != nil { - respondError(w, r, err.Error(), http.StatusForbidden) - return - } - _, err = s.api.FeedsUpdate(r.Context(), &updateRequest) - if err != nil { - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - fallthrough - case query.Get("manifest") == "1": - // we create a manifest so we can retrieve feed updates with bzz:// later - // this manifest has a special "feed type" manifest, and saves the - // feed identification used to retrieve feed updates later - m, err := s.api.NewFeedManifest(r.Context(), &updateRequest.Feed) - if err != nil { - respondError(w, r, fmt.Sprintf("failed to create feed manifest: %v", err), http.StatusInternalServerError) - return - } - // the key to the manifest will be passed back to the client - // the client can access the feed directly through its Feed member - // the manifest key can be set as content in the resolver of the ENS name - outdata, err := json.Marshal(m) - if err != nil { - respondError(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError) - return - } - fmt.Fprint(w, string(outdata)) - - w.Header().Add("Content-type", "application/json") - default: - respondError(w, r, "Missing signature in feed update request", http.StatusBadRequest) - } -} - -// HandleGetFeed retrieves Swarm feeds updates: -// bzz-feed://<manifest address or ENS name> - get latest feed update, given a manifest address -// - or - -// specify user + topic (optional), subtopic name (optional) directly, without manifest: -// bzz-feed://?user=0x...&topic=0x...&name=subtopic name -// topic defaults to 0x000... if not specified. -// name defaults to empty string if not specified. -// thus, empty name and topic refers to the user's default feed. -// -// Optional parameters: -// time=xx - get the latest update before time (in epoch seconds) -// hint.time=xx - hint the lookup algorithm looking for updates at around that time -// hint.level=xx - hint the lookup algorithm looking for updates at around this frequency level -// meta=1 - get feed metadata and status information instead of performing a feed query -// NOTE: meta=1 will be deprecated in the near future -func (s *Server) HandleGetFeed(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.get.feed", "ruid", ruid) - var err error - - fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query()) - if err != nil { // couldn't parse query string or retrieve manifest - getFail.Inc(1) - httpStatus := http.StatusBadRequest - if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI { - httpStatus = http.StatusNotFound - } - respondError(w, r, fmt.Sprintf("cannot retrieve feed information from manifest: %s", err), httpStatus) - return - } - - // determine if the query specifies period and version or it is a metadata query - if r.URL.Query().Get("meta") == "1" { - unsignedUpdateRequest, err := s.api.FeedsNewRequest(r.Context(), fd) - if err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot retrieve feed metadata for feed=%s: %s", fd.Hex(), err), http.StatusNotFound) - return - } - rawResponse, err := unsignedUpdateRequest.MarshalJSON() - if err != nil { - respondError(w, r, fmt.Sprintf("cannot encode unsigned feed update request: %v", err), http.StatusInternalServerError) - return - } - w.Header().Add("Content-type", "application/json") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, string(rawResponse)) - return - } - - lookupParams := &feed.Query{Feed: *fd} - if err = lookupParams.FromValues(r.URL.Query()); err != nil { // parse period, version - respondError(w, r, fmt.Sprintf("invalid feed update request:%s", err), http.StatusBadRequest) - return - } - - data, err := s.api.FeedsLookup(r.Context(), lookupParams) - - // any error from the switch statement will end up here - if err != nil { - code, err2 := s.translateFeedError(w, r, "feed lookup fail", err) - respondError(w, r, err2.Error(), code) - return - } - - // All ok, serve the retrieved update - log.Debug("Found update", "feed", fd.Hex(), "ruid", ruid) - w.Header().Set("Content-Type", api.MimeOctetStream) - http.ServeContent(w, r, "", time.Now(), bytes.NewReader(data)) -} - -func (s *Server) translateFeedError(w http.ResponseWriter, r *http.Request, supErr string, err error) (int, error) { - code := 0 - defaultErr := fmt.Errorf("%s: %v", supErr, err) - rsrcErr, ok := err.(*feed.Error) - if !ok && rsrcErr != nil { - code = rsrcErr.Code() - } - switch code { - case storage.ErrInvalidValue: - return http.StatusBadRequest, defaultErr - case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn, storage.ErrInit: - return http.StatusNotFound, defaultErr - case storage.ErrUnauthorized, storage.ErrInvalidSignature: - return http.StatusUnauthorized, defaultErr - case storage.ErrDataOverflow: - return http.StatusRequestEntityTooLarge, defaultErr - } - - return http.StatusInternalServerError, defaultErr -} - -// HandleGet handles a GET request to -// - bzz-raw://<key> and responds with the raw content stored at the -// given storage key -// - bzz-hash://<key> and responds with the hash of the content stored -// at the given storage key as a text/plain response -func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.get", "ruid", ruid, "uri", uri) - getCount.Inc(1) - _, pass, _ := r.BasicAuth() - - addr, err := s.api.ResolveURI(r.Context(), uri, pass) - if err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. - - log.Debug("handle.get: resolved", "ruid", ruid, "key", addr) - - // if path is set, interpret <key> as a manifest and return the - // raw entry at the given path - etag := common.Bytes2Hex(addr) - noneMatchEtag := r.Header.Get("If-None-Match") - w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key. - if noneMatchEtag != "" { - if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) { - w.WriteHeader(http.StatusNotModified) - return - } - } - - switch { - case uri.Raw(): - // check the root chunk exists by retrieving the file's size - reader, isEncrypted := s.api.Retrieve(r.Context(), addr) - if _, err := reader.Size(r.Context(), nil); err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound) - return - } - - w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted)) - - // allow the request to overwrite the content type using a query - // parameter - if typ := r.URL.Query().Get("content_type"); typ != "" { - w.Header().Set("Content-Type", typ) - } - http.ServeContent(w, r, "", time.Now(), reader) - case uri.Hash(): - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, addr) - } - -} - -// HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns -// a list of all files contained in <manifest> under <path> grouped into -// common prefixes using "/" as a delimiter -func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - log.Debug("handle.get.list", "ruid", ruid, "uri", uri) - getListCount.Inc(1) - - // ensure the root path has a trailing slash so that relative URLs work - if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently) - return - } - - addr, err := s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - getListFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - log.Debug("handle.get.list: resolved", "ruid", ruid, "key", addr) - - list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), addr, uri.Path) - if err != nil { - getListFail.Inc(1) - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", addr.String())) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - // if the client wants HTML (e.g. a browser) then render the list as a - // HTML index with relative URLs - if strings.Contains(r.Header.Get("Accept"), "text/html") { - w.Header().Set("Content-Type", "text/html") - err := TemplatesMap["bzz-list"].Execute(w, &htmlListData{ - URI: &api.URI{ - Scheme: "bzz", - Addr: uri.Addr, - Path: uri.Path, - }, - List: &list, - }) - if err != nil { - getListFail.Inc(1) - log.Error(fmt.Sprintf("error rendering list HTML: %s", err)) - } - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&list) -} - -// HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds -// with the content of the file at <path> from the given <manifest> -func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - log.Debug("handle.get.file", "ruid", ruid, "uri", r.RequestURI) - getFileCount.Inc(1) - - // ensure the root path has a trailing slash so that relative URLs work - if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently) - return - } - var err error - manifestAddr := uri.Address() - - if manifestAddr == nil { - manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - getFileFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - } else { - w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. - } - - log.Debug("handle.get.file: resolved", "ruid", ruid, "key", manifestAddr) - - reader, contentType, status, contentKey, err := s.api.Get(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path) - - etag := common.Bytes2Hex(contentKey) - noneMatchEtag := r.Header.Get("If-None-Match") - w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key. - if noneMatchEtag != "" { - if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) { - w.WriteHeader(http.StatusNotModified) - return - } - } - - if err != nil { - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr)) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - - switch status { - case http.StatusNotFound: - getFileNotFound.Inc(1) - respondError(w, r, err.Error(), http.StatusNotFound) - default: - getFileFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - } - return - } - - //the request results in ambiguous files - //e.g. /read with readme.md and readinglist.txt available in manifest - if status == http.StatusMultipleChoices { - list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path) - if err != nil { - getFileFail.Inc(1) - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr)) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", ruid) - //show a nice page links to available entries - ShowMultipleChoices(w, r, list) - return - } - - // check the root chunk exists by retrieving the file's size - if _, err := reader.Size(r.Context(), nil); err != nil { - getFileNotFound.Inc(1) - respondError(w, r, fmt.Sprintf("file not found %s: %s", uri, err), http.StatusNotFound) - return - } - - if contentType != "" { - w.Header().Set("Content-Type", contentType) - } - - fileName := uri.Addr - if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { - fileName = found - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) - - http.ServeContent(w, r, fileName, time.Now(), newBufferedReadSeeker(reader, getFileBufferSize)) -} - -// calculateNumberOfChunks calculates the number of chunks in an arbitrary content length -func calculateNumberOfChunks(contentLength int64, isEncrypted bool) int64 { - if contentLength < 4096 { - return 1 - } - branchingFactor := 128 - if isEncrypted { - branchingFactor = 64 - } - - dataChunks := math.Ceil(float64(contentLength) / float64(4096)) - totalChunks := dataChunks - intermediate := dataChunks / float64(branchingFactor) - - for intermediate > 1 { - totalChunks += math.Ceil(intermediate) - intermediate = intermediate / float64(branchingFactor) - } - - return int64(totalChunks) + 1 -} - -// The size of buffer used for bufio.Reader on LazyChunkReader passed to -// http.ServeContent in HandleGetFile. -// Warning: This value influences the number of chunk requests and chunker join goroutines -// per file request. -// Recommended value is 4 times the io.Copy default buffer value which is 32kB. -const getFileBufferSize = 4 * 32 * 1024 - -// bufferedReadSeeker wraps bufio.Reader to expose Seek method -// from the provied io.ReadSeeker in newBufferedReadSeeker. -type bufferedReadSeeker struct { - r io.Reader - s io.Seeker -} - -// newBufferedReadSeeker creates a new instance of bufferedReadSeeker, -// out of io.ReadSeeker. Argument `size` is the size of the read buffer. -func newBufferedReadSeeker(readSeeker io.ReadSeeker, size int) bufferedReadSeeker { - return bufferedReadSeeker{ - r: bufio.NewReaderSize(readSeeker, size), - s: readSeeker, - } -} - -func (b bufferedReadSeeker) Read(p []byte) (n int, err error) { - return b.r.Read(p) -} - -func (b bufferedReadSeeker) Seek(offset int64, whence int) (int64, error) { - return b.s.Seek(offset, whence) -} - -type loggingResponseWriter struct { - http.ResponseWriter - statusCode int -} - -func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { - return &loggingResponseWriter{w, http.StatusOK} -} - -func (lrw *loggingResponseWriter) WriteHeader(code int) { - lrw.statusCode = code - lrw.ResponseWriter.WriteHeader(code) -} - -func isDecryptError(err error) bool { - return strings.Contains(err.Error(), api.ErrDecrypt.Error()) -} diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go deleted file mode 100644 index 9df820ce4..000000000 --- a/swarm/api/http/server_test.go +++ /dev/null @@ -1,1409 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "archive/tar" - "bytes" - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "math/big" - "mime/multipart" - "net/http" - "net/url" - "os" - "path" - "strconv" - "strings" - "testing" - "time" - - "github.com/ethereum/go-ethereum/swarm/storage/feed/lookup" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/ethereum/go-ethereum/swarm/testutil" -) - -func init() { - loglevel := flag.Int("loglevel", 2, "loglevel") - flag.Parse() - log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) -} - -func serverFunc(api *api.API) TestServer { - return NewServer(api, "") -} - -func newTestSigner() (*feed.GenericSigner, error) { - privKey, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - if err != nil { - return nil, err - } - return feed.NewGenericSigner(privKey), nil -} - -// Test the transparent resolving of feed updates with bzz:// scheme -// -// First upload data to bzz:, and store the Swarm hash to the resulting manifest in a feed update. -// This effectively uses a feed to store a pointer to content rather than the content itself -// Retrieving the update with the Swarm hash should return the manifest pointing directly to the data -// and raw retrieve of that hash should return the data -func TestBzzWithFeed(t *testing.T) { - - signer, _ := newTestSigner() - - // Initialize Swarm test server - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // put together some data for our test: - dataBytes := []byte(` - // - // Create some data our manifest will point to. Data that could be very big and wouldn't fit in a feed update. - // So what we are going to do is upload it to Swarm bzz:// and obtain a **manifest hash** pointing to it: - // - // MANIFEST HASH --> DATA - // - // Then, we store that **manifest hash** into a Swarm Feed update. Once we have done this, - // we can use the **feed manifest hash** in bzz:// instead, this way: bzz://feed-manifest-hash. - // - // FEED MANIFEST HASH --> MANIFEST HASH --> DATA - // - // Given that we can update the feed at any time with a new **manifest hash** but the **feed manifest hash** - // stays constant, we have effectively created a fixed address to changing content. (Applause) - // - // FEED MANIFEST HASH (the same) --> MANIFEST HASH(2) --> DATA(2) ... - // - `) - - // POST data to bzz and get back a content-addressed **manifest hash** pointing to it. - resp, err := http.Post(fmt.Sprintf("%s/bzz:/", srv.URL), "text/plain", bytes.NewReader([]byte(dataBytes))) - if err != nil { - t.Fatal(err) - } - - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - manifestAddressHex, err := ioutil.ReadAll(resp.Body) - - if err != nil { - t.Fatal(err) - } - - manifestAddress := common.FromHex(string(manifestAddressHex)) - - log.Info("added data", "manifest", string(manifestAddressHex)) - - // At this point we have uploaded the data and have a manifest pointing to it - // Now store that manifest address in a feed update. - // We also want a feed manifest, so we can use it to refer to the feed. - - // First, create a topic for our feed: - topic, _ := feed.NewTopic("interesting topic indeed", nil) - - // Create a feed update request: - updateRequest := feed.NewFirstRequest(topic) - - // Store the **manifest address** as data into the feed update. - updateRequest.SetData(manifestAddress) - - // Sign the update - if err := updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - log.Info("added data", "data", common.ToHex(manifestAddress)) - - // Build the feed update http request: - feedUpdateURL, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - query := feedUpdateURL.Query() - body := updateRequest.AppendValues(query) // this adds all query parameters and returns the data to be posted - query.Set("manifest", "1") // indicate we want a feed manifest back - feedUpdateURL.RawQuery = query.Encode() - - // submit the feed update request to Swarm - resp, err = http.Post(feedUpdateURL.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - - feedManifestAddressHex, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - feedManifestAddress := &storage.Address{} - err = json.Unmarshal(feedManifestAddressHex, feedManifestAddress) - if err != nil { - t.Fatalf("data %s could not be unmarshaled: %v", feedManifestAddressHex, err) - } - - correctManifestAddrHex := "747c402e5b9dc715a25a4393147512167bab018a007fad7cdcd9adc7fce1ced2" - if feedManifestAddress.Hex() != correctManifestAddrHex { - t.Fatalf("Response feed manifest address mismatch, expected '%s', got '%s'", correctManifestAddrHex, feedManifestAddress.Hex()) - } - - // get bzz manifest transparent feed update resolve - getBzzURL := fmt.Sprintf("%s/bzz:/%s", srv.URL, feedManifestAddress) - resp, err = http.Get(getBzzURL) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - retrievedData, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(retrievedData, []byte(dataBytes)) { - t.Fatalf("retrieved data mismatch, expected %x, got %x", dataBytes, retrievedData) - } -} - -// Test Swarm feeds using the raw update methods -func TestBzzFeed(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - signer, _ := newTestSigner() - - defer srv.Close() - - // data of update 1 - update1Data := testutil.RandomBytes(1, 666) - update1Timestamp := srv.CurrentTime - //data for update 2 - update2Data := []byte("foo") - - topic, _ := feed.NewTopic("foo.eth", nil) - updateRequest := feed.NewFirstRequest(topic) - updateRequest.SetData(update1Data) - - if err := updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - - // creates feed and sets update 1 - testUrl, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - urlQuery := testUrl.Query() - body := updateRequest.AppendValues(urlQuery) // this adds all query parameters - urlQuery.Set("manifest", "1") // indicate we want a manifest back - testUrl.RawQuery = urlQuery.Encode() - - resp, err := http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - rsrcResp := &storage.Address{} - err = json.Unmarshal(b, rsrcResp) - if err != nil { - t.Fatalf("data %s could not be unmarshaled: %v", b, err) - } - - correctManifestAddrHex := "bb056a5264c295c2b0f613c8409b9c87ce9d71576ace02458160df4cc894210b" - if rsrcResp.Hex() != correctManifestAddrHex { - t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctManifestAddrHex, rsrcResp.Hex()) - } - - // get the manifest - testRawUrl := fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, rsrcResp) - resp, err = http.Get(testRawUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - manifest := &api.Manifest{} - err = json.Unmarshal(b, manifest) - if err != nil { - t.Fatal(err) - } - if len(manifest.Entries) != 1 { - t.Fatalf("Manifest has %d entries", len(manifest.Entries)) - } - correctFeedHex := "0x666f6f2e65746800000000000000000000000000000000000000000000000000c96aaa54e2d44c299564da76e1cd3184a2386b8d" - if manifest.Entries[0].Feed.Hex() != correctFeedHex { - t.Fatalf("Expected manifest Feed '%s', got '%s'", correctFeedHex, manifest.Entries[0].Feed.Hex()) - } - - // take the chance to have bzz: crash on resolving a feed update that does not contain - // a swarm hash: - testBzzUrl := fmt.Sprintf("%s/bzz:/%s", srv.URL, rsrcResp) - resp, err = http.Get(testBzzUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - t.Fatal("Expected error status since feed update does not contain a Swarm hash. Received 200 OK") - } - _, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - // get non-existent name, should fail - testBzzResUrl := fmt.Sprintf("%s/bzz-feed:/bar", srv.URL) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - - if resp.StatusCode != http.StatusNotFound { - t.Fatalf("Expected get non-existent feed manifest to fail with StatusNotFound (404), got %d", resp.StatusCode) - } - - resp.Body.Close() - - // get latest update through bzz-feed directly - log.Info("get update latest = 1.1", "addr", correctManifestAddrHex) - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update1Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update1Data, b) - } - - // update 2 - // Move the clock ahead 1 second - srv.CurrentTime++ - log.Info("update 2") - - // 1.- get metadata about this feed - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s/", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl + "?meta=1") - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("Get feed metadata returned %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - updateRequest = &feed.Request{} - if err = updateRequest.UnmarshalJSON(b); err != nil { - t.Fatalf("Error decoding feed metadata: %s", err) - } - updateRequest.SetData(update2Data) - if err = updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - testUrl, err = url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - urlQuery = testUrl.Query() - body = updateRequest.AppendValues(urlQuery) // this adds all query parameters - goodQueryParameters := urlQuery.Encode() // save the query parameters for a second attempt - - // create bad query parameters in which the signature is missing - urlQuery.Del("signature") - testUrl.RawQuery = urlQuery.Encode() - - // 1st attempt with bad query parameters in which the signature is missing - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode := http.StatusBadRequest - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // 2nd attempt with bad query parameters in which the signature is of incorrect length - urlQuery.Set("signature", "0xabcd") // should be 130 hex chars - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode = http.StatusBadRequest - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // 3rd attempt, with good query parameters: - testUrl.RawQuery = goodQueryParameters - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode = http.StatusOK - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // get latest update through bzz-feed directly - log.Info("get update 1.2") - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update2Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update2Data, b) - } - - // test manifest-less queries - log.Info("get first update in update1Timestamp via direct query") - query := feed.NewQuery(&updateRequest.Feed, update1Timestamp, lookup.NoClue) - - urlq, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - - values := urlq.Query() - query.AppendValues(values) // this adds feed query parameters - urlq.RawQuery = values.Encode() - resp, err = http.Get(urlq.String()) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update1Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update1Data, b) - } - -} - -func TestBzzGetPath(t *testing.T) { - testBzzGetPath(false, t) - testBzzGetPath(true, t) -} - -func testBzzGetPath(encrypted bool, t *testing.T) { - var err error - - testmanifest := []string{ - `{"entries":[{"path":"b","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"c","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0}]}`, - `{"entries":[{"path":"a","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"b/","hash":"<key0>","contentType":"application/bzz-manifest+json","status":0}]}`, - `{"entries":[{"path":"a/","hash":"<key1>","contentType":"application/bzz-manifest+json","status":0}]}`, - } - - testrequests := make(map[string]int) - testrequests["/"] = 2 - testrequests["/a/"] = 1 - testrequests["/a/b/"] = 0 - testrequests["/x"] = 0 - testrequests[""] = 0 - - expectedfailrequests := []string{"", "/x"} - - reader := [3]*bytes.Reader{} - - addr := [3]storage.Address{} - - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for i, mf := range testmanifest { - reader[i] = bytes.NewReader([]byte(mf)) - var wait func(context.Context) error - ctx := context.TODO() - addr[i], wait, err = srv.FileStore.Store(ctx, reader[i], int64(len(mf)), encrypted) - if err != nil { - t.Fatal(err) - } - for j := i + 1; j < len(testmanifest); j++ { - testmanifest[j] = strings.Replace(testmanifest[j], fmt.Sprintf("<key%v>", i), addr[i].Hex(), -1) - } - err = wait(ctx) - if err != nil { - t.Fatal(err) - } - } - - rootRef := addr[2].Hex() - - _, err = http.Get(srv.URL + "/bzz-raw:/" + rootRef + "/a") - if err != nil { - t.Fatalf("Failed to connect to proxy: %v", err) - } - - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz-raw:/" - if k != "" { - url += rootRef + "/" + k[1:] + "?content_type=text/plain" - } - resp, err = http.Get(url) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Error while reading response body: %v", err) - } - - if string(respbody) != testmanifest[v] { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Fatalf("Response body does not match, expected: %v, got %v", testmanifest[v], string(respbody)) - } - } - } - - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz-hash:/" - if k != "" { - url += rootRef + "/" + k[1:] - } - resp, err = http.Get(url) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read request body: %v", err) - } - - if string(respbody) != addr[v].Hex() { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Fatalf("Response body does not match, expected: %v, got %v", addr[v], string(respbody)) - } - } - } - - ref := addr[2].Hex() - - for _, c := range []struct { - path string - json string - pageFragments []string - }{ - { - path: "/", - json: `{"common_prefixes":["a/"]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/", ref), - `<a class="normal-link" href="a/">a/</a>`, - }, - }, - { - path: "/a/", - json: `{"common_prefixes":["a/b/"],"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/a","mod_time":"0001-01-01T00:00:00Z"}]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/a/", ref), - `<a class="normal-link" href="b/">b/</a>`, - fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/a">a</a>`, ref), - }, - }, - { - path: "/a/b/", - json: `{"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/b/b","mod_time":"0001-01-01T00:00:00Z"},{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/b/c","mod_time":"0001-01-01T00:00:00Z"}]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/a/b/", ref), - fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/b">b</a>`, ref), - fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/c">c</a>`, ref), - }, - }, - { - path: "/x", - }, - { - path: "", - }, - } { - k := c.path - url := srv.URL + "/bzz-list:/" - if k != "" { - url += rootRef + "/" + k[1:] - } - t.Run("json list "+c.path, func(t *testing.T) { - resp, err := http.Get(url) - if err != nil { - t.Fatalf("HTTP request: %v", err) - } - defer resp.Body.Close() - respbody, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read response body: %v", err) - } - - body := strings.TrimSpace(string(respbody)) - if body != c.json { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Errorf("Response list body %q does not match, expected: %v, got %v", k, c.json, body) - } - } - }) - t.Run("html list "+c.path, func(t *testing.T) { - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - t.Fatalf("New request: %v", err) - } - req.Header.Set("Accept", "text/html") - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("HTTP request: %v", err) - } - defer resp.Body.Close() - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read response body: %v", err) - } - - body := string(b) - - for _, f := range c.pageFragments { - if !strings.Contains(body, f) { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Errorf("Response list body %q does not contain %q: body %q", k, f, body) - } - } - } - }) - } - - nonhashtests := []string{ - srv.URL + "/bzz:/name", - srv.URL + "/bzz-immutable:/nonhash", - srv.URL + "/bzz-raw:/nonhash", - srv.URL + "/bzz-list:/nonhash", - srv.URL + "/bzz-hash:/nonhash", - } - - nonhashresponses := []string{ - `cannot resolve name: no DNS to resolve name: "name"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - } - - for i, url := range nonhashtests { - var resp *http.Response - var respbody []byte - - resp, err = http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("ReadAll failed: %v", err) - } - if !strings.Contains(string(respbody), nonhashresponses[i]) { - t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) - } - } -} - -func TestBzzTar(t *testing.T) { - testBzzTar(false, t) - testBzzTar(true, t) -} - -func testBzzTar(encrypted bool, t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"} - fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} - - buf := &bytes.Buffer{} - tw := tar.NewWriter(buf) - defer tw.Close() - - for i, v := range fileNames { - size := int64(len(fileContents[i])) - hdr := &tar.Header{ - Name: v, - Mode: 0644, - Size: size, - ModTime: time.Now(), - Xattrs: map[string]string{ - "user.swarm.content-type": "text/plain", - }, - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - // copy the file into the tar stream - n, err := io.Copy(tw, bytes.NewBufferString(fileContents[i])) - if err != nil { - t.Fatal(err) - } else if n != size { - t.Fatal("size mismatch") - } - } - - //post tar stream - url := srv.URL + "/bzz:/" - if encrypted { - url = url + "encrypt" - } - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/x-tar") - req.Header.Add(SwarmTagHeaderName, "test-upload") - client := &http.Client{} - resp2, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - if resp2.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp2.Status) - } - - // check that the tag was written correctly - tag := srv.Tags.All()[0] - testutil.CheckTag(t, tag, 4, 4, 0, 4) - - swarmHash, err := ioutil.ReadAll(resp2.Body) - resp2.Body.Close() - if err != nil { - t.Fatal(err) - } - - // now do a GET to get a tarball back - req, err = http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s", string(swarmHash)), nil) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Accept", "application/x-tar") - resp2, err = client.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp2.Body.Close() - - if h := resp2.Header.Get("Content-Type"); h != "application/x-tar" { - t.Fatalf("Content-Type header expected: application/x-tar, got: %s", h) - } - - expectedFileName := string(swarmHash) + ".tar" - expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", expectedFileName) - if h := resp2.Header.Get("Content-Disposition"); h != expectedContentDisposition { - t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) - } - - file, err := ioutil.TempFile("", "swarm-downloaded-tarball") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - _, err = io.Copy(file, resp2.Body) - if err != nil { - t.Fatalf("error getting tarball: %v", err) - } - file.Sync() - file.Close() - - tarFileHandle, err := os.Open(file.Name()) - if err != nil { - t.Fatal(err) - } - tr := tar.NewReader(tarFileHandle) - - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - t.Fatalf("error reading tar stream: %s", err) - } - bb := make([]byte, hdr.Size) - _, err = tr.Read(bb) - if err != nil && err != io.EOF { - t.Fatal(err) - } - passed := false - for i, v := range fileNames { - if v == hdr.Name { - if string(bb) == fileContents[i] { - passed = true - break - } - } - } - if !passed { - t.Fatalf("file %s did not pass content assertion", hdr.Name) - } - } - - // now check the tags endpoint -} - -// TestBzzCorrectTagEstimate checks that the HTTP middleware sets the total number of chunks -// in the tag according to an estimate from the HTTP request Content-Length header divided -// by chunk size (4096). It is needed to be checked BEFORE chunking is done, therefore -// concurrency was introduced to slow down the HTTP request -func TestBzzCorrectTagEstimate(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for _, v := range []struct { - toEncrypt bool - expChunks int64 - }{ - {toEncrypt: false, expChunks: 248}, - {toEncrypt: true, expChunks: 250}, - } { - pr, pw := io.Pipe() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - addr := "" - if v.toEncrypt { - addr = "encrypt" - } - req, err := http.NewRequest("POST", srv.URL+"/bzz:/"+addr, pr) - if err != nil { - t.Fatal(err) - } - - req = req.WithContext(ctx) - req.ContentLength = 1000000 - req.Header.Add(SwarmTagHeaderName, "1000000") - - go func() { - for { - select { - case <-ctx.Done(): - return - case <-time.After(1 * time.Millisecond): - _, err := pw.Write([]byte{0}) - if err != nil { - t.Error(err) - } - } - } - }() - go func() { - transport := http.DefaultTransport - _, err := transport.RoundTrip(req) - if err != nil { - t.Error(err) - } - }() - done := false - for !done { - switch len(srv.Tags.All()) { - case 0: - <-time.After(10 * time.Millisecond) - case 1: - tag := srv.Tags.All()[0] - testutil.CheckTag(t, tag, 0, 0, 0, v.expChunks) - srv.Tags.Delete(tag.Uid) - done = true - } - } - } -} - -// TestBzzRootRedirect tests that getting the root path of a manifest without -// a trailing slash gets redirected to include the trailing slash so that -// relative URLs work as expected. -func TestBzzRootRedirect(t *testing.T) { - testBzzRootRedirect(false, t) -} -func TestBzzRootRedirectEncrypted(t *testing.T) { - testBzzRootRedirect(true, t) -} - -func testBzzRootRedirect(toEncrypt bool, t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // create a manifest with some data at the root path - data := []byte("data") - headers := map[string]string{"Content-Type": "text/plain"} - res, hash := httpDo("POST", srv.URL+"/bzz:/", bytes.NewReader(data), headers, false, t) - if res.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code from server %d want %d", res.StatusCode, http.StatusOK) - } - - // define a CheckRedirect hook which ensures there is only a single - // redirect to the correct URL - redirected := false - httpClient := http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - if redirected { - return errors.New("too many redirects") - } - redirected = true - expectedPath := "/bzz:/" + hash + "/" - if req.URL.Path != expectedPath { - return fmt.Errorf("expected redirect to %q, got %q", expectedPath, req.URL.Path) - } - return nil - }, - } - - // perform the GET request and assert the response - res, err := httpClient.Get(srv.URL + "/bzz:/" + hash) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - if !redirected { - t.Fatal("expected GET /bzz:/<hash> to redirect to /bzz:/<hash>/ but it didn't") - } - gotData, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(gotData, data) { - t.Fatalf("expected response to equal %q, got %q", data, gotData) - } -} - -func TestMethodsNotAllowed(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - databytes := "bar" - for _, c := range []struct { - url string - code int - }{ - { - url: fmt.Sprintf("%s/bzz-list:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, { - url: fmt.Sprintf("%s/bzz-hash:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, - { - url: fmt.Sprintf("%s/bzz-immutable:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, - } { - res, _ := http.Post(c.url, "text/plain", bytes.NewReader([]byte(databytes))) - if res.StatusCode != c.code { - t.Fatalf("should have failed. requested url: %s, expected code %d, got %d", c.url, c.code, res.StatusCode) - } - } - -} - -func httpDo(httpMethod string, url string, reqBody io.Reader, headers map[string]string, verbose bool, t *testing.T) (*http.Response, string) { - // Build the Request - req, err := http.NewRequest(httpMethod, url, reqBody) - if err != nil { - t.Fatal(err) - } - for key, value := range headers { - req.Header.Set(key, value) - } - if verbose { - t.Log(req.Method, req.URL, req.Header, req.Body) - } - - // Send Request out - httpClient := &http.Client{} - res, err := httpClient.Do(req) - if err != nil { - t.Fatal(err) - } - - // Read the HTTP Body - buffer, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - body := string(buffer) - - return res, body -} - -func TestGet(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for _, testCase := range []struct { - uri string - method string - headers map[string]string - expectedStatusCode int - assertResponseBody string - verbose bool - }{ - { - uri: fmt.Sprintf("%s/", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "text/html"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "Swarm provides censorship resistant storage and communication infrastructure for a sovereign digital society", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "application/json"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/robots.txt", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "text/html"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "User-agent: *\nDisallow: /", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/nonexistent_path", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:asdf/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/tbz2/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-rack:/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-ls", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }} { - t.Run("GET "+testCase.uri, func(t *testing.T) { - res, body := httpDo(testCase.method, testCase.uri, nil, testCase.headers, testCase.verbose, t) - if res.StatusCode != testCase.expectedStatusCode { - t.Fatalf("expected status code %d but got %d", testCase.expectedStatusCode, res.StatusCode) - } - if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { - t.Fatalf("expected response to be: %s but got: %s", testCase.assertResponseBody, body) - } - }) - } -} - -func TestModify(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - headers := map[string]string{"Content-Type": "text/plain"} - res, hash := httpDo("POST", srv.URL+"/bzz:/", bytes.NewReader([]byte("data")), headers, false, t) - if res.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code from server %d want %d", res.StatusCode, http.StatusOK) - } - - for _, testCase := range []struct { - uri string - method string - headers map[string]string - requestBody []byte - expectedStatusCode int - assertResponseBody string - assertResponseHeaders map[string]string - verbose bool - }{ - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "DELETE", - headers: map[string]string{}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "8b634aea26eec353ac0ecbec20c94f44d6f8d11f38d4578a4c207a84c74ef731", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "PUT", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, hash), - method: "PUT", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "PATCH", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/", srv.URL), - method: "POST", - headers: map[string]string{}, - requestBody: []byte("POSTdata"), - expectedStatusCode: http.StatusOK, - assertResponseHeaders: map[string]string{"Content-Length": "64"}, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/encrypt", srv.URL), - method: "POST", - headers: map[string]string{}, - requestBody: []byte("POSTdata"), - expectedStatusCode: http.StatusOK, - assertResponseHeaders: map[string]string{"Content-Length": "128"}, - verbose: false, - }, - } { - t.Run(testCase.method+" "+testCase.uri, func(t *testing.T) { - reqBody := bytes.NewReader(testCase.requestBody) - res, body := httpDo(testCase.method, testCase.uri, reqBody, testCase.headers, testCase.verbose, t) - - if res.StatusCode != testCase.expectedStatusCode { - t.Fatalf("expected status code %d but got %d, %s", testCase.expectedStatusCode, res.StatusCode, body) - } - if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { - t.Log(body) - t.Fatalf("expected response %s but got %s", testCase.assertResponseBody, body) - } - for key, value := range testCase.assertResponseHeaders { - if res.Header.Get(key) != value { - t.Logf("expected %s=%s in HTTP response header but got %s", key, value, res.Header.Get(key)) - } - } - }) - } -} - -func TestMultiPartUpload(t *testing.T) { - // POST /bzz:/ Content-Type: multipart/form-data - verbose := false - // Setup Swarm - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - url := fmt.Sprintf("%s/bzz:/", srv.URL) - - buf := new(bytes.Buffer) - form := multipart.NewWriter(buf) - form.WriteField("name", "John Doe") - file1, _ := form.CreateFormFile("cv", "cv.txt") - file1.Write([]byte("John Doe's Credentials")) - file2, _ := form.CreateFormFile("profile_picture", "profile.jpg") - file2.Write([]byte("imaginethisisjpegdata")) - form.Close() - - headers := map[string]string{ - "Content-Type": form.FormDataContentType(), - "Content-Length": strconv.Itoa(buf.Len()), - } - res, body := httpDo("POST", url, buf, headers, verbose, t) - - if res.StatusCode != http.StatusOK { - t.Fatalf("expected POST multipart/form-data to return 200, but it returned %d", res.StatusCode) - } - if len(body) != 64 { - t.Fatalf("expected POST multipart/form-data to return a 64 char manifest but the answer was %d chars long", len(body)) - } -} - -// TestBzzGetFileWithResolver tests fetching a file using a mocked ENS resolver -func TestBzzGetFileWithResolver(t *testing.T) { - resolver := newTestResolveValidator("") - srv := NewTestSwarmServer(t, serverFunc, resolver) - defer srv.Close() - fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"} - fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} - - buf := &bytes.Buffer{} - tw := tar.NewWriter(buf) - - for i, v := range fileNames { - size := len(fileContents[i]) - hdr := &tar.Header{ - Name: v, - Mode: 0644, - Size: int64(size), - ModTime: time.Now(), - Xattrs: map[string]string{ - "user.swarm.content-type": "text/plain", - }, - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - // copy the file into the tar stream - n, err := io.WriteString(tw, fileContents[i]) - if err != nil { - t.Fatal(err) - } else if n != size { - t.Fatal("size mismatch") - } - } - - if err := tw.Close(); err != nil { - t.Fatal(err) - } - - //post tar stream - url := srv.URL + "/bzz:/" - - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/x-tar") - client := &http.Client{} - serverResponse, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - if serverResponse.StatusCode != http.StatusOK { - t.Fatalf("err %s", serverResponse.Status) - } - swarmHash, err := ioutil.ReadAll(serverResponse.Body) - serverResponse.Body.Close() - if err != nil { - t.Fatal(err) - } - // set the resolved hash to be the swarm hash of what we've just uploaded - hash := common.HexToHash(string(swarmHash)) - resolver.hash = &hash - for _, v := range []struct { - addr string - path string - expectedStatusCode int - expectedContentType string - expectedFileName string - }{ - { - addr: string(swarmHash), - path: fileNames[0], - expectedStatusCode: http.StatusOK, - expectedContentType: "text/plain", - expectedFileName: path.Base(fileNames[0]), - }, - { - addr: "somebogusensname", - path: fileNames[0], - expectedStatusCode: http.StatusOK, - expectedContentType: "text/plain", - expectedFileName: path.Base(fileNames[0]), - }, - } { - req, err := http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s/%s", v.addr, v.path), nil) - if err != nil { - t.Fatal(err) - } - serverResponse, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - defer serverResponse.Body.Close() - if serverResponse.StatusCode != v.expectedStatusCode { - t.Fatalf("expected %d, got %d", v.expectedStatusCode, serverResponse.StatusCode) - } - - if h := serverResponse.Header.Get("Content-Type"); h != v.expectedContentType { - t.Fatalf("Content-Type header expected: %s, got %s", v.expectedContentType, h) - } - - expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", v.expectedFileName) - if h := serverResponse.Header.Get("Content-Disposition"); h != expectedContentDisposition { - t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) - } - - } -} - -// TestCalculateNumberOfChunks is a unit test for the chunk-number-according-to-content-length -// calculation -func TestCalculateNumberOfChunks(t *testing.T) { - - //test cases: - for _, tc := range []struct{ len, chunks int64 }{ - {len: 1000, chunks: 1}, - {len: 5000, chunks: 3}, - {len: 10000, chunks: 4}, - {len: 100000, chunks: 26}, - {len: 1000000, chunks: 248}, - {len: 325839339210, chunks: 79550620 + 621490 + 4856 + 38 + 1}, - } { - res := calculateNumberOfChunks(tc.len, false) - if res != tc.chunks { - t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res) - } - } -} - -// TestCalculateNumberOfChunksEncrypted is a unit test for the chunk-number-according-to-content-length -// calculation with encryption (branching factor=64) -func TestCalculateNumberOfChunksEncrypted(t *testing.T) { - - //test cases: - for _, tc := range []struct{ len, chunks int64 }{ - {len: 1000, chunks: 1}, - {len: 5000, chunks: 3}, - {len: 10000, chunks: 4}, - {len: 100000, chunks: 26}, - {len: 1000000, chunks: 245 + 4 + 1}, - {len: 325839339210, chunks: 79550620 + 1242979 + 19422 + 304 + 5 + 1}, - } { - res := calculateNumberOfChunks(tc.len, true) - if res != tc.chunks { - t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res) - } - } -} - -// testResolver implements the Resolver interface and either returns the given -// hash if it is set, or returns a "name not found" error -type testResolveValidator struct { - hash *common.Hash -} - -func newTestResolveValidator(addr string) *testResolveValidator { - r := &testResolveValidator{} - if addr != "" { - hash := common.HexToHash(addr) - r.hash = &hash - } - return r -} - -func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) { - if t.hash == nil { - return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) - } - return *t.hash, nil -} - -func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) { - return -} - -func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) { - return -} diff --git a/swarm/api/http/templates.go b/swarm/api/http/templates.go deleted file mode 100644 index 19c4040ca..000000000 --- a/swarm/api/http/templates.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "encoding/hex" - "fmt" - "html/template" - "path" - - "github.com/ethereum/go-ethereum/swarm/api" -) - -type htmlListData struct { - URI *api.URI - List *api.ManifestList -} - -var TemplatesMap = make(map[string]*template.Template) -var faviconBytes []byte - -func init() { - for _, v := range []struct { - templateName string - partial string - funcs template.FuncMap - }{ - { - templateName: "error", - partial: errorResponse, - }, - { - templateName: "bzz-list", - partial: bzzList, - funcs: template.FuncMap{ - "basename": path.Base, - "leaflink": leafLink, - }, - }, - { - templateName: "landing-page", - partial: landing, - }, - } { - TemplatesMap[v.templateName] = template.Must(template.New(v.templateName).Funcs(v.funcs).Parse(baseTemplate + css + v.partial + logo)) - } - - bytes, err := hex.DecodeString(favicon) - if err != nil { - panic(err) - } - faviconBytes = bytes -} - -func leafLink(URI api.URI, manifestEntry api.ManifestEntry) string { - return fmt.Sprintf("/bzz:/%s/%s", URI.Addr, manifestEntry.Path) -} - -const bzzList = `{{ define "content" }} -<h3 class="top-space">Swarm index of {{ .URI }}</h3> -<hr> -<table> - <thead> - <tr> - <th>Path</th> - <th>Type</th> - <th>Size</th> - </tr> - </thead> - - <tbody> - {{ range .List.CommonPrefixes }} - <tr> - <td> - <a class="normal-link" href="{{ basename . }}/">{{ basename . }}/</a> - </td> - <td>DIR</td> - <td>-</td> - </tr> - {{ end }} - {{ range .List.Entries }} - <tr> - <td> - <a class="normal-link" href="{{ leaflink $.URI . }}">{{ basename .Path }}</a> - </td> - <td>{{ .ContentType }}</td> - <td>{{ .Size }}</td> - </tr> - {{ end }} -</table> -<hr> - - {{ end }}` - -const errorResponse = `{{ define "content" }} -<div class="container"> - <div class="logo"> - {{ template "logo" . }} - </div> - - <div class="separate-block"> - <h3>{{.Msg}}</h3> - </div> - - <div> - <h5>Error code: {{.Code}}</h5> - </div> - - <div class="footer"> - <p>{{.Timestamp}}</p> - <p>Swarm provides censorship resistant storage and communication infrastructure for a sovereign digital society</p> - </div> - </div> -{{ end }}` - -const landing = `{{ define "content" }} - -<script type="text/javascript"> -function goToPage() { - var page = document.getElementById('page').value; - if (page == "") { - var page = "theswarm.eth" - } - var address = "/bzz:/" + page; - location.href = address; - console.log(address) -} -</script> - -<div class="container"> -<div class="logo"> - {{ template "logo" . }} -</div> - - -<div class="searchbar"> - <form class="separate-block" action="javascript:goToPage();"> - <input type="text" id="page" autofocus name="search" placeholder="Please enter an ENS name or swarm hash to retrieve .."> - <button class="button" type="submit" value="submit" onclick="goToPage();">Go!</button> - </form> -</div> -<div class="footer"> -<p>{{.Timestamp}}</p> -<p>Swarm provides censorship resistant storage and communication infrastructure for a sovereign digital society</p> -</div> -</div> - -{{ end }}` - -const baseTemplate = `<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/> - <meta http-equiv="X-UA-Compatible" ww="chrome=1"/> - <link rel="icon" type="image/x-icon" href="favicon.ico"/> - <style> - {{ template "css" . }} - </style> -</head> -<body> - {{ template "content" . }} -</body> -</html> -` - -const css = `{{ define "css" }} -html { - font-size: 18px; - font-size: 1.13rem; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - font-family: Helvetica, Arial, sans-serif; -} - -body { - background: #f6f6f6; - color: #333; -} - -a, a:visited, a:active { - color: darkorange; -} - -a.normal-link, a.normal-link:active { color: #0000EE; } -a.normal-link:visited { color: #551A8B; } - -table { - border-collapse: separate; -} - -td { - padding: 3px 10px; -} - - -.container { - max-width: 600px; - margin: 40px auto 40px; - text-align: center; -} - -.separate-block { - margin: 40px 0; - word-wrap: break-word; -} - -.footer { - font-size: 12px; - font-size: 0.75rem; - text-align: center; -} - -.orange { - color: #ffa500; -} - -.top-space { - margin-top: 20px; - margin-bottom: 20px; -} - -/* SVG Logos, editable */ - -.searchbar { - padding: 20px 20px 0; -} - -.logo { - margin: 100px 80px 0; -} - -.logo a img { - max-width: 140px; -} - -/* Tablet < 600p*/ - -@media only screen and (max-width: 600px) {} - -/* Mobile phone < 360p*/ - -@media only screen and (max-width: 360px) { - h1 { - font-size: 20px; - font-size: 1.5rem; - } - h2 { - font-size: 0.88rem; - margin: 0; - } - .logo { - margin: 50px 40px 0; - } - .footer { - font-size: 0.63rem; - text-align: center; - } -} - -input[type=text] { - width: 100%; - box-sizing: border-box; - border: 2px solid #777; - border-radius: 2px; - font-size: 16px; - padding: 12px 20px 12px 20px; - transition: border 250ms ease-in-out; -} - -input[type=text]:focus { - border: 2px solid #ffce73; -} - -.button { - background-color: #ffa500; - margin: 20px 0; - border: none; - border-radius: 2px; - color: #222; - padding: 15px 32px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; -} -{{ end }}` - -const logo = `{{ define "logo" }} -<a href="/bzz:/theswarm.eth"><img src="data:image/png;base64,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"/></a> -{{ end }}` - -const favicon = `000001000400101000000000200068040000460000002020000000002000a8100000ae0400003030000000002000a825000056150000404000000000200028420000fe3a000028000000100000002000000001002000000000004004000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e0362626263545454c548484849ffffff01ffffff01ffffff01ffffff01646464375b5b5bbf4545457758585809ffffff01ffffff01ffffff0164646443626262cf626262ff535353ff454545ff454545b74949492b6868681d626262a5626262fd5c5c5cff464646ff454545dd47474755ffffff01ffffff013f3f3feb565656ff636363ff535353ff464646ff3f3f3fff373737ab393939894d4d4dff626262ff5c5c5cff464646ff424242ff3a3a3af7ffffff01ffffff01383838e9353535ff424242ff474747ff383838ff353535ff363636ab35353587363636ff3a3a3aff4a4a4aff3b3b3bff353535ff363636f5ffffff01ffffff01383838e9303030ff181818ff131313ff232323ff343434ff363636ab35353587343434ff202020ff101010ff1d1d1dff303030ff373737f5ffffff01ffffff01232323c50c0c0cff0d0d0dff131313ff171717ff171717ff2929298b2727276b0f0f0ffd0d0d0dff101010ff171717ff161616ff232323d9ffffff01ffffff014d4d4d030f0f0f650c0c0ce7131313ff161616d51d1d1d4b63636363464646691717173b0d0d0dc50f0f0fff161616ef171717752e2e2e07ffffff01ffffff01ffffff01ffffff011d1d1d0f1515155360606045626262cf636363ff464646ff454545d3484848491414144d24242417ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013c3c3c374f4f4fff636363ff636363ff464646ff464646ff3f3f3fff3c3c3c41ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636363d353535ff3c3c3cff575757ff363636ff181818ff282828ff37373747ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636363d363636ff303030ff181818ff292929ff131313ef17171771696969136565653bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01323232371e1e1eff0d0d0dff0c0c0cff363636ff363636a3ffffff0185858515606060ff4747476bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01111111450d0d0dd10c0c0cff1b1b1bff2a2a2a993e3e3e0b30303085292929ff37373787ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636030e0e0e671616166b45454505323232432e2e2ed9151515c31d1d1d2dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e05ffffff01ffffff01ffffff01ffffff010000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff28000000200000004000000001002000000000008010000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017272721b646464a54646466f72727205ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0168686845575757b74f4f4f39ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e0b6262627d616161f3636363ff424242ff444444d74f4f4f49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016c6c6c27636363b5616161ff555555ff434343ff464646a35858581dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016666665d616161e3626262ff636363ff636363ff444444ff464646ff434343ff454545b95252522bffffff01ffffff01ffffff01ffffff016c6c6c1363636393616161fb636363ff636363ff555555ff464646ff464646ff444444f5464646836666660bffffff01ffffff01ffffff01ffffff01ffffff016a6a6a3f626262c9616161ff636363ff636363ff636363ff636363ff444444ff464646ff464646ff464646ff434343fb48484897545454135b5b5b036868686f616161ef626262ff636363ff636363ff636363ff555555ff464646ff464646ff464646ff454545ff444444e54a4a4a5fffffff01ffffff01ffffff01ffffff013b3b3bd7505050ff646464ff636363ff636363ff636363ff636363ff444444ff464646ff464646ff464646ff454545ff3a3a3aff33333357313131113c3c3cff5a5a5aff646464ff636363ff636363ff636363ff555555ff464646ff464646ff464646ff464646ff424242ff383838f1ffffff01ffffff01ffffff01ffffff013a3a3ad5353535ff3a3a3aff575757ff646464ff626262ff636363ff444444ff464646ff464646ff3d3d3dff353535ff363636ff3636365535353511363636ff343434ff434343ff606060ff636363ff636363ff555555ff464646ff464646ff444444ff393939ff353535ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff343434ff3f3f3fff5d5d5dff646464ff444444ff404040ff363636ff353535ff363636ff363636ff3636365535353511363636ff363636ff363636ff343434ff4a4a4aff636363ff555555ff454545ff3c3c3cff353535ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff363636ff363636ff353535ff3f3f3fff363636ff353535ff363636ff363636ff363636ff363636ff3636365535353511363636ff363636ff363636ff363636ff353535ff383838ff3a3a3aff373737ff353535ff363636ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff363636ff323232ff181818ff0e0e0eff171717ff282828ff373737ff363636ff363636ff363636ff3636365535353511363636ff363636ff353535ff373737ff292929ff0f0f0fff111111ff1b1b1bff2f2f2fff373737ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff1e1e1eff0b0b0bff0d0d0dff0f0f0fff171717ff161616ff191919ff2c2c2cff373737ff363636ff3636365535353511363636ff373737ff2f2f2fff141414ff0b0b0bff0d0d0dff131313ff171717ff151515ff1f1f1fff333333ff363636ff373737edffffff01ffffff01ffffff01ffffff013b3b3bd5252525ff0d0d0dff0c0c0cff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff151515ff1c1c1cff313131ff3535355734343411333333ff1a1a1aff0b0b0bff0d0d0dff0d0d0dff0d0d0dff131313ff171717ff171717ff171717ff161616ff242424ff373737efffffff01ffffff01ffffff01ffffff012020205d0b0b0be50b0b0bff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff131313ff161616b73333331f3b3b3b05111111970a0a0afb0d0d0dff0d0d0dff0d0d0dff0d0d0dff131313ff171717ff171717ff171717ff161616ff141414f51c1c1c7fffffff01ffffff01ffffff01ffffff01ffffff014d4d4d0b1212127f0a0a0af50d0d0dff0d0d0dff0f0f0fff171717ff171717ff151515ff151515d522222249ffffff017373731b51515121ffffff011d1d1d2b101010b50a0a0aff0d0d0dff0d0d0dff131313ff171717ff171717ff131313ff181818a12e2e2e1dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012c2c2c1b0f0f0fa10a0a0afd0f0f0fff161616ff141414e91b1b1b69656565057878780b6363637b626262f3464646f7454545896969690fffffff011c1c1c470c0c0cd30b0b0bff131313ff141414ff151515c32a2a2a37ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011d1d1d35111111bd1a1a1a8d2f2f2f11ffffff0166666659616161e1626262ff646464ff474747ff454545ff444444e9494949677b7b7b054040400517171769131313cd24242455ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0169696939626262c7616161ff636363ff636363ff646464ff474747ff464646ff464646ff444444ff454545d14e4e4e45ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01424242615e5e5eff636363ff636363ff636363ff636363ff646464ff474747ff464646ff464646ff464646ff464646ff434343ff3f3f3f77ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679343434ff494949ff636363ff636363ff636363ff646464ff474747ff464646ff464646ff474747ff3d3d3dff353535ff3a3a3a8dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff353535ff363636ff505050ff646464ff636363ff474747ff484848ff2f2f2fff1c1c1cff323232ff363636ff3a3a3a8dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff353535ff3a3a3aff5a5a5aff393939ff0f0f0fff040404ff111111ff151515ff232323ff3535358fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff363636ff323232ff171717ff2a2a2aff0c0c0cff030303ff111111ff141414fb171717992e2e2e17a3a3a305ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff1f1f1fff0b0b0bff0d0d0dff363636ff383838ff242424ff121212bf2a2a2a2dffffff01ffffff018484842b636363bf6d6d6d2fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679373737ff252525ff0d0d0dff0c0c0cff0d0d0dff0d0d0dff373737ff363636ff353535ff39393949ffffff01ffffff01ffffff0186868629646464ff656565fb6464649b55555505ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012e2e2e650e0e0eff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0c0c0cff353535ff363636ff353535ff37373749ffffff01ffffff01ffffff0185858529656565ff525252ff353535ff4b4b4b0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011c1c1c430d0d0dcf0b0b0bff0d0d0dff0d0d0dff0d0d0dff171717ff282828ff363636ff37373749ffffff01ffffff01ffffff0144444459363636ff353535ff353535ff4e4e4e0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0162626203161616630b0b0be70c0c0cff0d0d0dff171717ff161616ff171717ed3737372fffffff013e3e3e2b303030b72a2a2aff151515ff262626ff363636ff4b4b4b0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636360d101010850a0a0af7141414f91717178f45454511ffffff014c4c4c252c2c2cdb303030ff2d2d2dff151515ff131313ff1b1b1bad5a5a5a07ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012b2b2b2121212127ffffff01ffffff01ffffff01ffffff0161616109313131752b2b2bf1131313cd26262641ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e1359595903ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000300000006000000001002000000000008025000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0173737357545454997c7c7c11ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767663515151916c6c6c0dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017676762d636363bb636363ff4d4d4dff434343eb4f4f4f6d7f7f7f05ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767635616161c3626262ff494949ff424242e94f4f4f6392929203ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e19626262955f5f5ffd626262ff666666ff4f4f4fff464646ff424242ff434343d75a5a5a49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017777771d6464649f5f5f5fff636363ff656565ff4b4b4bff464646ff424242ff444444d158585841ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018585850966666677606060ef626262ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff414141ff464646b75d5d5d2dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018989890d6868687f5f5f5ff5626262ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff404040ff484848b160606027ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016a6a6a55626262df606060ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff454545ff424242fd484848956a6a6a17ffffff01ffffff01ffffff01ffffff01ffffff016969695f606060e3606060ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff454545ff414141f94a4a4a8d65656513ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016e6e6e3b656565c15f5f5fff636363ff636363ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242ed52525277ffffff01ffffff016c6c6c37676767c95f5f5fff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff464646ff464646ff434343ff444444e94d4d4d6dffffff01ffffff01ffffff01ffffff01ffffff01ffffff013c3c3cc5454545ff646464ff646464ff636363ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff464646ff464646ff474747ff424242ff333333fb34343409ffffff0131313199494949ff656565ff646464ff636363ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff464646ff464646ff474747ff414141ff373737ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf333333ff343434ff4f4f4fff666666ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff474747ff444444ff383838ff343434ff363636f737373707ffffff0135353597343434ff343434ff525252ff666666ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff474747ff444444ff383838ff343434ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff333333ff383838ff585858ff676767ff636363ff636363ff666666ff4f4f4fff464646ff464646ff474747ff464646ff3b3b3bff343434ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff333333ff383838ff5a5a5aff666666ff636363ff636363ff656565ff4b4b4bff464646ff464646ff474747ff454545ff3a3a3aff343434ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff323232ff3d3d3dff5d5d5dff666666ff666666ff4f4f4fff464646ff474747ff3e3e3eff353535ff353535ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff313131ff3f3f3fff5f5f5fff666666ff656565ff4b4b4bff464646ff474747ff3d3d3dff353535ff353535ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff363636ff353535ff323232ff444444ff676767ff525252ff404040ff363636ff353535ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff363636ff353535ff323232ff464646ff676767ff4e4e4eff404040ff363636ff353535ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff363636ff363636ff353535ff383838ff2d2d2dff2b2b2bff373737ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff363636ff363636ff363636ff383838ff2c2c2cff2a2a2aff373737ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff353535ff383838ff343434ff171717ff090909ff151515ff171717ff2d2d2dff383838ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff353535ff383838ff333333ff151515ff090909ff151515ff181818ff2f2f2fff383838ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff373737ff373737ff1f1f1fff090909ff0c0c0cff0c0c0cff171717ff171717ff141414ff1b1b1bff323232ff383838ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff373737ff373737ff1d1d1dff0a0a0aff0c0c0cff0c0c0cff171717ff171717ff141414ff1c1c1cff333333ff383838ff353535ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff393939ff272727ff0c0c0cff0b0b0bff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff161616ff141414ff202020ff353535ff373737ff363636ff363636f737373707ffffff0135353597363636ff363636ff383838ff252525ff0b0b0bff0b0b0bff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff161616ff141414ff222222ff363636ff373737ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf383838ff2d2d2dff101010ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff161616ff141414ff262626ff373737ff373737f737373707ffffff0136363697393939ff2b2b2bff0f0f0fff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff161616ff151515ff272727ff383838ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013a3a3abd131313ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff171717ff262626fb38383807ffffff012a2a2a97121212ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff161616ff2a2a2ae7ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015f5f5f0b1616167b090909ef0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff0f0f0fff181818b74040402dffffff01ffffff014646461118181883080808f30b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff161616ff101010ff181818b141414127ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d171212129b090909fd0c0c0cff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff111111ff141414d335353547ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013838381d131313a5060606ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff111111ff181818cd2e2e2e3dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01333333310f0f0fbb070707ff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff141414ff121212e72424246d86868603ffffff01ffffff017373732b656565b9464646c95e5e5e3bffffff01ffffff01ffffff01323232370e0e0ec3080808ff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff121212ff161616e525252563ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012525254d0e0e0ed9090909ff0c0c0cff171717ff151515ff121212f91d1d1d894d4d4d13ffffff01ffffff0178787815656565935f5f5ffb646464ff484848ff404040ff454545a96a6a6a1fffffff01ffffff01ffffff011b1b1b570e0e0edf080808ff0d0d0dff171717ff151515ff0f0f0ff3212121815656560dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01636363071a1a1a710a0a0aed0f0f0fff1b1b1bad2f2f2f23ffffff01ffffff018d8d8d0566666675616161eb616161ff636363ff646464ff484848ff464646ff454545ff424242f54c4c4c856262620fffffff01ffffff014040400b21212179080808f10f0f0fff1b1b1ba15757571dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014141411740404037ffffff01ffffff01ffffff016a6a6a4d616161db606060ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff434343ff434343e751515167ffffff01ffffff01ffffff014646461d30303033ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767631616161c35f5f5fff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff424242ff454545d158585841ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015252527f636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff434343ff454545a1ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01313131b53b3b3bff5b5b5bff676767ff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff474747ff444444ff393939ff383838d3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff323232ff404040ff616161ff656565ff626262ff636363ff636363ff646464ff484848ff464646ff464646ff454545ff494949ff474747ff3b3b3bff343434ff353535ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff353535ff323232ff484848ff656565ff646464ff636363ff646464ff484848ff464646ff474747ff494949ff242424ff282828ff383838ff363636ff363636ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff343434ff343434ff515151ff666666ff656565ff484848ff4b4b4bff323232ff070707ff040404ff151515ff181818ff2f2f2fff383838ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff363636ff363636ff333333ff383838ff5f5f5fff3c3c3cff0f0f0fff020202ff050505ff050505ff171717ff171717ff141414ff1c1c1cff323232d7ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff363636ff353535ff383838ff343434ff161616ff2a2a2aff0c0c0cff020202ff050505ff050505ff171717ff171717ff101010ff161616bf2e2e2e35ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff373737ff383838ff1f1f1fff0a0a0aff0c0c0cff373737ff3a3a3aff262626ff060606ff040404ff121212ff151515dd30303051ffffff01ffffff01ffffff018787872d6b6b6b47ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff393939ff272727ff0d0d0dff0b0b0bff0d0d0dff0d0d0dff373737ff363636ff373737ff383838ff1c1c1cf92020207568686807ffffff01ffffff01ffffff01ffffff018686863d5f5f5fff676767af77777721ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff393939ff2e2e2eff101010ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff373737ff363636ff363636ff353535ff373737ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018686863d626262ff666666ff646464f76969698d9494940fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01383838b5333333ff161616ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff373737ff363636ff363636ff363636ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018686863d626262ff676767ff6b6b6bff555555ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0125252589030303ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff333333ff383838ff353535ff363636ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018585853d666666ff5f5f5fff3c3c3cff313131ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012d2d2d3f0e0e0ecb080808ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff141414ff222222ff363636ff373737ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff0177777741414141ff313131ff363636ff353535ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011e1e1e5f0a0a0ae50a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff171717ff161616ff151515ff282828ff353535f3ffffff01ffffff01ffffff01ffffff016e6e6e0b37373781242424f1191919ff333333ff383838ff343434ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015a5a5a0d1919197f0a0a0af30b0b0bff0d0d0dff0d0d0dff171717ff171717ff161616ff0f0f0ffb24242489ffffff01ffffff01ffffff013e3e3e5d2d2d2de52e2e2eff2b2b2bff151515ff141414ff212121ff363636ff3b3b3b95ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636361b111111a3080808ff0c0c0cff181818ff0f0f0fff171717b545454525ffffff01ffffff017f7f7f05363636c7282828ff313131ff313131ff2b2b2bff151515ff171717ff161616ff0c0c0cfb3434346bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01303030350f0f0fc7121212d337373741ffffff01ffffff01ffffff01ffffff01ffffff016b6b6b0b3a3a3a7d2c2c2cf12f2f2fff2b2b2bff151515ff101010ff171717bb4646462dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01515151193535359b242424ff131313d72828284bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e2b59595905ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff28000000400000008000000001002000000000000042000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767635666666914e4e4e457c7c7c09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018080801569696989545454696c6c6c0bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018484840d70707061616161d5606060fb3d3d3ddf4e4e4e9172727213ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017070704d626262b35f5f5ffb464646f1454545a16a6a6a33ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017676760f67676753646464cf5e5e5eff656565ff626262ff414141ff404040ff444444e54b4b4b7b69696919ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01979797036c6c6c45676767a95d5d5dff616161ff626262ff484848ff424242ff3e3e3efd4e4e4e8958585831ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e2b616161a75f5f5fef616161ff636363ff656565ff626262ff424242ff464646ff444444ff414141fd434343b961616153ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017777771969696981606060e7606060ff636363ff636363ff626262ff484848ff464646ff454545ff424242fd414141d95656566569696911ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01858585056e6e6e29656565995f5f5ff1616161ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff444444ff3f3f3fff484848af5353534b86868607ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01797979216a6a6a6f616161ed5e5e5eff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff3e3e3eff474747d75151515762626213ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01838383036f6f6f755f5f5fd3606060ff626262ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff454545ff434343ff404040e94e4e4e8d5f5f5f1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018f8f8f056b6b6b45616161c95f5f5ff7616161ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff444444ff424242f1434343b16666662dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017070700f6969695f626262d35e5e5eff626262ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff404040ff444444f14d4d4d776a6a6a23ffffff01ffffff01ffffff01ffffff017b7b7b096c6c6c39636363c15f5f5ffb626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff434343ff414141f54a4a4aa35b5b5b2d70707007ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0171717143676767a7616161f3616161ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff414141f7474747cd54545447ffffff01ffffff015b5b5b096b6b6b99646464e1606060ff626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242ff414141d552525277ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040b33b3b3bff5c5c5cff656565ff646464ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff454545ff3a3a3aff313131ad34343407ffffff012e2e2e25383838ff535353ff656565ff656565ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff464646ff3b3b3bff3a3a3ae9ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9313131ff363636ff484848ff636363ff676767ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff404040ff363636ff343434ff353535a537373705ffffff0135353521333333ff333333ff434343ff5c5c5cff686868ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff484848ff414141ff393939ff313131ff3c3c3cdbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff323232ff353535ff4b4b4bff636363ff656565ff636363ff626262ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff474747ff464646ff414141ff363636ff343434ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff333333ff313131ff484848ff5e5e5eff666666ff646464ff626262ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff474747ff424242ff3a3a3aff343434ff353535ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff343434ff333333ff3d3d3dff555555ff686868ff656565ff626262ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff484848ff444444ff393939ff353535ff353535ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff353535ff323232ff363636ff515151ff646464ff656565ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff484848ff454545ff3d3d3dff353535ff343434ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff343434ff303030ff3f3f3fff575757ff666666ff656565ff646464ff626262ff424242ff464646ff474747ff454545ff3a3a3aff343434ff353535ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff303030ff373737ff535353ff636363ff656565ff636363ff626262ff484848ff464646ff474747ff454545ff3e3e3eff353535ff343434ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff333333ff484848ff606060ff696969ff626262ff434343ff474747ff3e3e3eff363636ff353535ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff353535ff343434ff333333ff3e3e3eff5d5d5dff686868ff626262ff484848ff474747ff424242ff373737ff353535ff353535ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff323232ff323232ff505050ff616161ff3d3d3dff373737ff343434ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff343434ff313131ff434343ff606060ff464646ff383838ff343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff3a3a3aff2b2b2bff1e1e1eff2d2d2dff383838ff373737ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff393939ff323232ff1c1c1cff262626ff373737ff383838ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff353535ff373737ff383838ff303030ff191919ff080808ff101010ff141414ff1a1a1aff303030ff383838ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff353535ff363636ff383838ff363636ff1d1d1dff0b0b0bff0c0c0cff141414ff181818ff292929ff373737ff373737ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff353535ff393939ff363636ff222222ff0c0c0cff0a0a0aff0c0c0cff121212ff171717ff151515ff161616ff212121ff353535ff393939ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff353535ff383838ff3a3a3aff262626ff121212ff0a0a0aff0c0c0cff0f0f0fff171717ff151515ff151515ff1e1e1eff2f2f2fff3a3a3aff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff383838ff363636ff262626ff0d0d0dff090909ff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff141414ff151515ff232323ff353535ff383838ff363636ff353535ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff353535ff383838ff383838ff292929ff131313ff080808ff0c0c0cff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff151515ff131313ff202020ff313131ff383838ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff353535ff363636ff3a3a3aff2e2e2eff131313ff0a0a0aff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff161616ff141414ff1a1a1aff2a2a2aff393939ff373737ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff3a3a3aff313131ff1c1c1cff0a0a0aff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff161616ff151515ff161616ff282828ff363636ff383838ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9353535ff383838ff313131ff151515ff080808ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff161616ff131313ff1b1b1bff2d2d2dff373737ff373737ff363636a537373705ffffff0134343421363636ff383838ff333333ff1e1e1eff090909ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff131313ff171717ff2a2a2aff363636ff353535ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444af353535ff1e1e1eff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff151515ff222222ff333333ff353535ad30303007ffffff0134343423373737ff282828ff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff141414ff1b1b1bff2e2e2eff3e3e3ee1ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013e3e3e6f0f0f0fd5040404ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff101010ff0e0e0ee72f2f2f7347474703ffffff013b3b3b13141414cd050505f70a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff121212ff0c0c0cf12a2a2aa5ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015f5f5f052020202b1a1a1aa1080808f1070707ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff141414ff0c0c0cff212121af2a2a2a496d6d6d07ffffff01ffffff01ffffff01333333231d1d1d730b0b0beb060606ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff0e0e0eff181818d72626265546464615ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d29121212af080808ef0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff141414ff121212f9141414b93b3b3b4fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0138383819151515890a0a0ae5080808ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff161616ff101010fb151515d72c2c2c614444440dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0133333311262626510f0f0fd7050505ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff101010ff141414e7242424733a3a3a19ffffff01ffffff01ffffff01878787097272725f4d4d4d736a6a6a11ffffff01ffffff01ffffff016060600524242445191919ad040404ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff111111ff0e0e0efd242424873232322dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015c5c5c0d2525255f090909d7080808fb0b0b0bff0d0d0dff0c0c0cff121212ff171717ff171717ff161616ff121212ff121212df2121218965656511ffffff01ffffff01ffffff018080800d6767674b646464d1606060ff454545ff464646df4f4f4f6165656517ffffff01ffffff01ffffff01ffffff012d2d2d4b101010b5060606fb0a0a0aff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff161616ff131313ff101010ef2020209d4242422dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012c2c2c2d1f1f1f83080808fb080808ff0d0d0dff121212ff171717ff141414ff0f0f0ff91e1e1eb12c2c2c354d4d4d09ffffff01ffffff01ffffff0178787825646464a75f5f5feb616161ff656565ff4a4a4aff414141ff424242f3414141bd69696937ffffff01ffffff01ffffff01ffffff0142424219171717710d0d0de3060606ff0c0c0cff0f0f0fff171717ff151515ff0d0d0dff171717c3292929575656560dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013737372d1212129d080808ef0d0d0dff121212f5191919bf2e2e2e3d70707003ffffff01ffffff018c8c8c037676762564646497606060ed606060ff636363ff636363ff656565ff4a4a4aff444444ff464646ff444444ff404040f74a4a4aad5555553162626207ffffff01ffffff01ffffff014040401125252589090909dd0a0a0aff121212ff141414c738383869ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015b5b5b0b1f1f1f591d1d1daf292929673f3f3f19ffffff01ffffff01ffffff01ffffff016d6d6d715f5f5fcd606060ff626262ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff454545ff434343ff414141db4f4f4f857b7b7b11ffffff01ffffff01ffffff0153535307222222331d1d1da91b1b1b8d4141412365656503ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017c7c7c0f6868685d636363cb5e5e5eff626262ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff404040ff454545e14c4c4c6b69696917ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0177777733626262a3606060f3616161ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242f9454545b55d5d5d49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014b4b4b0f5e5e5e85626262ff626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff414141ff454545a16464641dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0132323225333333cf4e4e4eff646464ff666666ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff404040ff303030e35757573bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd313131ff363636ff515151ff636363ff656565ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff414141ff373737ff343434ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff343434ff333333ff3c3c3cff5b5b5bff686868ff636363ff626262ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff454545ff464646ff4c4c4cff454545ff393939ff353535ff353535ff353535ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff353535ff313131ff3f3f3fff5d5d5dff666666ff646464ff626262ff636363ff656565ff4a4a4aff444444ff454545ff474747ff4a4a4aff404040ff212121ff2f2f2fff373737ff373737ff353535ff363636ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff353535ff333333ff363636ff484848ff646464ff676767ff626262ff656565ff4a4a4aff444444ff4b4b4bff4a4a4aff262626ff0b0b0bff090909ff171717ff252525ff353535ff393939ff363636ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff323232ff363636ff4c4c4cff646464ff676767ff4d4d4dff484848ff2c2c2cff0b0b0bff020202ff040404ff0b0b0bff171717ff141414ff161616ff282828ff353535ff343434e359595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff343434ff323232ff3f3f3fff5f5f5fff3a3a3aff161616ff030303ff030303ff050505ff040404ff0b0b0bff171717ff171717ff161616ff151515ff1a1a1aff242424e55555553bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff353535ff363636ff383838ff2e2e2eff191919ff262626ff111111ff030303ff030303ff050505ff040404ff0b0b0bff171717ff171717ff151515ff111111f9121212cd272727557d7d7d09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff383838ff373737ff242424ff0b0b0bff0a0a0aff393939ff393939ff222222ff080808ff020202ff030303ff0b0b0bff181818ff0f0f0fff151515f32424247935353525ffffff01ffffff01ffffff01a3a3a30fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff383838ff373737ff272727ff0c0c0cff090909ff0c0c0cff0e0e0eff373737ff363636ff3a3a3aff393939ff1e1e1eff080808ff080808ff0f0f0feb232323914040401dffffff01ffffff01ffffff01ffffff01ffffff018282825d626262c36d6d6d4d8d8d8d09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff353535ff363636ff3a3a3aff2f2f2fff131313ff0b0b0bff0b0b0bff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff353535ff363636ff393939ff303030ff1c1c1cc92626264d68686807ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01868686515e5e5eff646464e9696969957878781fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff373737ff383838ff313131ff161616ff090909ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff363636ff363636ff353535ff353535ff3c3c3c8fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff676767ff646464ff656565f16a6a6a7d7f7f7f25ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723353535cd393939ff373737ff1f1f1fff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff363636ff363636ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff676767ff666666ff676767ff686868f9555555cd55555511ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0134343425323232cf212121ff0e0e0eff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff383838ff363636ff363636ff363636ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff686868ff696969ff5f5f5fff3d3d3dff303030ff4848481dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01474747132323238f020202ff080808ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0c0c0cff2e2e2eff393939ff363636ff353535ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0185858551666666ff676767ff494949ff353535ff323232ff353535ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0130303045101010af080808f70a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0d0d0dff131313ff1c1c1cff303030ff373737ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0181818151494949ff363636ff313131ff363636ff353535ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0141414113191919690f0f0fdb060606ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0d0d0dff171717ff151515ff161616ff222222ff363636ff383838ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d53272727c1242424ff373737ff373737ff353535ff353535ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01626262091c1c1c830b0b0bd7090909ff0c0c0cff0d0d0dff0d0d0dff0c0c0cff0d0d0dff171717ff171717ff171717ff141414ff151515ff202020ff35353595ffffff01ffffff01ffffff01ffffff017474740540404049343434af2a2a2aff262626ff101010ff191919ff2e2e2eff373737ff363636ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015a5a5a073636362d141414a7080808f5080808ff0d0d0dff0c0c0cff0d0d0dff171717ff171717ff171717ff151515ff0e0e0efb1b1b1bbb3d3d3d29ffffff01ffffff01ffffff0151515119393939892a2a2ae92d2d2dff323232ff282828ff141414ff151515ff151515ff1f1f1fff343434ff393939ff4949491dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636362f111111b5070707f30a0a0aff0d0d0dff171717ff141414ff111111f5111111c74343433d70707005ffffff01ffffff017c7c7c034e4e4e632a2a2af7292929ff323232ff313131ff323232ff282828ff141414ff171717ff171717ff151515ff0e0e0efd222222e153535315ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012d2d2d151f1f1f590e0e0edb040404ff0f0f0fff171717e7262626673f3f3f1dffffff01ffffff01ffffff01ffffff01ffffff01444444293535358b2d2d2deb2b2b2bff313131ff323232ff282828ff141414ff171717ff121212ff0d0d0dff2222229d2626263dbebebe03ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01505050112626266f1d1d1d7f36363617ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01616161213333339d2c2c2ce92f2f2fff282828ff111111ff111111f7191919ab3c3c3c41ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015151510b3b3b3b43383838c51f1f1fff141414d71e1e1e654f4f4f13ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015858580b4d4d4d4159595909ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000` diff --git a/swarm/api/http/test_server.go b/swarm/api/http/test_server.go deleted file mode 100644 index a3be01e99..000000000 --- a/swarm/api/http/test_server.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/ethereum/go-ethereum/swarm/storage/localstore" -) - -type TestServer interface { - ServeHTTP(http.ResponseWriter, *http.Request) -} - -func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer, resolver api.Resolver) *TestSwarmServer { - swarmDir, err := ioutil.TempDir("", "swarm-storage-test") - if err != nil { - t.Fatal(err) - } - localStore, err := localstore.New(swarmDir, make([]byte, 32), nil) - if err != nil { - os.RemoveAll(swarmDir) - t.Fatal(err) - } - - tags := chunk.NewTags() - fileStore := storage.NewFileStore(localStore, storage.NewFileStoreParams(), tags) - - // Swarm feeds test setup - feedsDir, err := ioutil.TempDir("", "swarm-feeds-test") - if err != nil { - t.Fatal(err) - } - - feeds, err := feed.NewTestHandler(feedsDir, &feed.HandlerParams{}) - if err != nil { - t.Fatal(err) - } - - swarmApi := api.NewAPI(fileStore, resolver, feeds.Handler, nil, tags) - apiServer := httptest.NewServer(serverFunc(swarmApi)) - - tss := &TestSwarmServer{ - Server: apiServer, - FileStore: fileStore, - Tags: tags, - dir: swarmDir, - Hasher: storage.MakeHashFunc(storage.DefaultHash)(), - cleanup: func() { - apiServer.Close() - fileStore.Close() - feeds.Close() - os.RemoveAll(swarmDir) - os.RemoveAll(feedsDir) - }, - CurrentTime: 42, - } - feed.TimestampProvider = tss - return tss -} - -type TestSwarmServer struct { - *httptest.Server - Hasher storage.SwarmHash - FileStore *storage.FileStore - Tags *chunk.Tags - dir string - cleanup func() - CurrentTime uint64 -} - -func (t *TestSwarmServer) Close() { - t.cleanup() -} - -func (t *TestSwarmServer) Now() feed.Timestamp { - return feed.Timestamp{Time: t.CurrentTime} -} diff --git a/swarm/api/inspector.go b/swarm/api/inspector.go deleted file mode 100644 index c4151bf20..000000000 --- a/swarm/api/inspector.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "context" - "fmt" - "strings" - "time" - - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/network" - "github.com/ethereum/go-ethereum/swarm/storage" -) - -type Inspector struct { - api *API - hive *network.Hive - netStore *storage.NetStore -} - -func NewInspector(api *API, hive *network.Hive, netStore *storage.NetStore) *Inspector { - return &Inspector{api, hive, netStore} -} - -// Hive prints the kademlia table -func (inspector *Inspector) Hive() string { - return inspector.hive.String() -} - -func (inspector *Inspector) ListKnown() []string { - res := []string{} - for _, v := range inspector.hive.Kademlia.ListKnown() { - res = append(res, fmt.Sprintf("%v", v)) - } - return res -} - -func (inspector *Inspector) IsSyncing() bool { - lastReceivedChunksMsg := metrics.GetOrRegisterGauge("network.stream.received_chunks", nil) - - // last received chunks msg time - lrct := time.Unix(0, lastReceivedChunksMsg.Value()) - - // if last received chunks msg time is after now-15sec. (i.e. within the last 15sec.) then we say that the node is still syncing - // technically this is not correct, because this might have been a retrieve request, but for the time being it works for our purposes - // because we know we are not making retrieve requests on the node while checking this - return lrct.After(time.Now().Add(-15 * time.Second)) -} - -// Has checks whether each chunk address is present in the underlying datastore, -// the bool in the returned structs indicates if the underlying datastore has -// the chunk stored with the given address (true), or not (false) -func (inspector *Inspector) Has(chunkAddresses []storage.Address) string { - hostChunks := []string{} - for _, addr := range chunkAddresses { - has, err := inspector.netStore.Has(context.Background(), addr) - if err != nil { - log.Error(err.Error()) - } - if has { - hostChunks = append(hostChunks, "1") - } else { - hostChunks = append(hostChunks, "0") - } - } - - return strings.Join(hostChunks, "") -} diff --git a/swarm/api/manifest.go b/swarm/api/manifest.go deleted file mode 100644 index d753b3f2e..000000000 --- a/swarm/api/manifest.go +++ /dev/null @@ -1,584 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strings" - "time" - - "github.com/ethereum/go-ethereum/swarm/storage/feed" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/storage" -) - -const ( - ManifestType = "application/bzz-manifest+json" - FeedContentType = "application/bzz-feed" - - manifestSizeLimit = 5 * 1024 * 1024 -) - -// Manifest represents a swarm manifest -type Manifest struct { - Entries []ManifestEntry `json:"entries,omitempty"` -} - -// ManifestEntry represents an entry in a swarm manifest -type ManifestEntry struct { - Hash string `json:"hash,omitempty"` - Path string `json:"path,omitempty"` - ContentType string `json:"contentType,omitempty"` - Mode int64 `json:"mode,omitempty"` - Size int64 `json:"size,omitempty"` - ModTime time.Time `json:"mod_time,omitempty"` - Status int `json:"status,omitempty"` - Access *AccessEntry `json:"access,omitempty"` - Feed *feed.Feed `json:"feed,omitempty"` -} - -// ManifestList represents the result of listing files in a manifest -type ManifestList struct { - CommonPrefixes []string `json:"common_prefixes,omitempty"` - Entries []*ManifestEntry `json:"entries,omitempty"` -} - -// NewManifest creates and stores a new, empty manifest -func (a *API) NewManifest(ctx context.Context, toEncrypt bool) (storage.Address, error) { - var manifest Manifest - data, err := json.Marshal(&manifest) - if err != nil { - return nil, err - } - addr, wait, err := a.Store(ctx, bytes.NewReader(data), int64(len(data)), toEncrypt) - if err != nil { - return nil, err - } - err = wait(ctx) - return addr, err -} - -// Manifest hack for supporting Swarm feeds from the bzz: scheme -// see swarm/api/api.go:API.Get() for more information -func (a *API) NewFeedManifest(ctx context.Context, feed *feed.Feed) (storage.Address, error) { - var manifest Manifest - entry := ManifestEntry{ - Feed: feed, - ContentType: FeedContentType, - } - manifest.Entries = append(manifest.Entries, entry) - data, err := json.Marshal(&manifest) - if err != nil { - return nil, err - } - addr, wait, err := a.Store(ctx, bytes.NewReader(data), int64(len(data)), false) - if err != nil { - return nil, err - } - err = wait(ctx) - return addr, err -} - -// ManifestWriter is used to add and remove entries from an underlying manifest -type ManifestWriter struct { - api *API - trie *manifestTrie - quitC chan bool -} - -func (a *API) NewManifestWriter(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWriter, error) { - trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - return nil, fmt.Errorf("error loading manifest %s: %s", addr, err) - } - return &ManifestWriter{a, trie, quitC}, nil -} - -// AddEntry stores the given data and adds the resulting address to the manifest -func (m *ManifestWriter) AddEntry(ctx context.Context, data io.Reader, e *ManifestEntry) (addr storage.Address, err error) { - entry := newManifestTrieEntry(e, nil) - if data != nil { - var wait func(context.Context) error - addr, wait, err = m.api.Store(ctx, data, e.Size, m.trie.encrypted) - if err != nil { - return nil, err - } - err = wait(ctx) - if err != nil { - return nil, err - } - entry.Hash = addr.Hex() - } - if entry.Hash == "" { - return addr, errors.New("missing entry hash") - } - m.trie.addEntry(entry, m.quitC) - return addr, nil -} - -// RemoveEntry removes the given path from the manifest -func (m *ManifestWriter) RemoveEntry(path string) error { - m.trie.deleteEntry(path, m.quitC) - return nil -} - -// Store stores the manifest, returning the resulting storage address -func (m *ManifestWriter) Store() (storage.Address, error) { - return m.trie.ref, m.trie.recalcAndStore() -} - -// ManifestWalker is used to recursively walk the entries in the manifest and -// all of its submanifests -type ManifestWalker struct { - api *API - trie *manifestTrie - quitC chan bool -} - -func (a *API) NewManifestWalker(ctx context.Context, addr storage.Address, decrypt DecryptFunc, quitC chan bool) (*ManifestWalker, error) { - trie, err := loadManifest(ctx, a.fileStore, addr, quitC, decrypt) - if err != nil { - return nil, fmt.Errorf("error loading manifest %s: %s", addr, err) - } - return &ManifestWalker{a, trie, quitC}, nil -} - -// ErrSkipManifest is used as a return value from WalkFn to indicate that the -// manifest should be skipped -var ErrSkipManifest = errors.New("skip this manifest") - -// WalkFn is the type of function called for each entry visited by a recursive -// manifest walk -type WalkFn func(entry *ManifestEntry) error - -// Walk recursively walks the manifest calling walkFn for each entry in the -// manifest, including submanifests -func (m *ManifestWalker) Walk(walkFn WalkFn) error { - return m.walk(m.trie, "", walkFn) -} - -func (m *ManifestWalker) walk(trie *manifestTrie, prefix string, walkFn WalkFn) error { - for _, entry := range &trie.entries { - if entry == nil { - continue - } - entry.Path = prefix + entry.Path - err := walkFn(&entry.ManifestEntry) - if err != nil { - if entry.ContentType == ManifestType && err == ErrSkipManifest { - continue - } - return err - } - if entry.ContentType != ManifestType { - continue - } - if err := trie.loadSubTrie(entry, nil); err != nil { - return err - } - if err := m.walk(entry.subtrie, entry.Path, walkFn); err != nil { - return err - } - } - return nil -} - -type manifestTrie struct { - fileStore *storage.FileStore - entries [257]*manifestTrieEntry // indexed by first character of basePath, entries[256] is the empty basePath entry - ref storage.Address // if ref != nil, it is stored - encrypted bool - decrypt DecryptFunc -} - -func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry { - return &manifestTrieEntry{ - ManifestEntry: *entry, - subtrie: subtrie, - } -} - -type manifestTrieEntry struct { - ManifestEntry - - subtrie *manifestTrie -} - -func loadManifest(ctx context.Context, fileStore *storage.FileStore, addr storage.Address, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand - log.Trace("manifest lookup", "addr", addr) - // retrieve manifest via FileStore - manifestReader, isEncrypted := fileStore.Retrieve(ctx, addr) - log.Trace("reader retrieved", "addr", addr) - return readManifest(manifestReader, addr, fileStore, isEncrypted, quitC, decrypt) -} - -func readManifest(mr storage.LazySectionReader, addr storage.Address, fileStore *storage.FileStore, isEncrypted bool, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand - // TODO check size for oversized manifests - size, err := mr.Size(mr.Context(), quitC) - if err != nil { // size == 0 - // can't determine size means we don't have the root chunk - log.Trace("manifest not found", "addr", addr) - err = fmt.Errorf("Manifest not Found") - return - } - if size > manifestSizeLimit { - log.Warn("manifest exceeds size limit", "addr", addr, "size", size, "limit", manifestSizeLimit) - err = fmt.Errorf("Manifest size of %v bytes exceeds the %v byte limit", size, manifestSizeLimit) - return - } - manifestData := make([]byte, size) - read, err := mr.Read(manifestData) - if int64(read) < size { - log.Trace("manifest not found", "addr", addr) - if err == nil { - err = fmt.Errorf("Manifest retrieval cut short: read %v, expect %v", read, size) - } - return - } - - log.Debug("manifest retrieved", "addr", addr) - var man struct { - Entries []*manifestTrieEntry `json:"entries"` - } - err = json.Unmarshal(manifestData, &man) - if err != nil { - err = fmt.Errorf("Manifest %v is malformed: %v", addr.Log(), err) - log.Trace("malformed manifest", "addr", addr) - return - } - - log.Trace("manifest entries", "addr", addr, "len", len(man.Entries)) - - trie = &manifestTrie{ - fileStore: fileStore, - encrypted: isEncrypted, - decrypt: decrypt, - } - for _, entry := range man.Entries { - err = trie.addEntry(entry, quitC) - if err != nil { - return - } - } - return -} - -func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) error { - mt.ref = nil // trie modified, hash needs to be re-calculated on demand - - if entry.ManifestEntry.Access != nil { - if mt.decrypt == nil { - return errors.New("dont have decryptor") - } - - err := mt.decrypt(&entry.ManifestEntry) - if err != nil { - return err - } - } - - if len(entry.Path) == 0 { - mt.entries[256] = entry - return nil - } - - b := entry.Path[0] - oldentry := mt.entries[b] - if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) { - mt.entries[b] = entry - return nil - } - - cpl := 0 - for (len(entry.Path) > cpl) && (len(oldentry.Path) > cpl) && (entry.Path[cpl] == oldentry.Path[cpl]) { - cpl++ - } - - if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) { - if mt.loadSubTrie(oldentry, quitC) != nil { - return nil - } - entry.Path = entry.Path[cpl:] - oldentry.subtrie.addEntry(entry, quitC) - oldentry.Hash = "" - return nil - } - - commonPrefix := entry.Path[:cpl] - - subtrie := &manifestTrie{ - fileStore: mt.fileStore, - encrypted: mt.encrypted, - } - entry.Path = entry.Path[cpl:] - oldentry.Path = oldentry.Path[cpl:] - subtrie.addEntry(entry, quitC) - subtrie.addEntry(oldentry, quitC) - - mt.entries[b] = newManifestTrieEntry(&ManifestEntry{ - Path: commonPrefix, - ContentType: ManifestType, - }, subtrie) - return nil -} - -func (mt *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) { - for _, e := range &mt.entries { - if e != nil { - cnt++ - entry = e - } - } - return -} - -func (mt *manifestTrie) deleteEntry(path string, quitC chan bool) { - mt.ref = nil // trie modified, hash needs to be re-calculated on demand - - if len(path) == 0 { - mt.entries[256] = nil - return - } - - b := path[0] - entry := mt.entries[b] - if entry == nil { - return - } - if entry.Path == path { - mt.entries[b] = nil - return - } - - epl := len(entry.Path) - if (entry.ContentType == ManifestType) && (len(path) >= epl) && (path[:epl] == entry.Path) { - if mt.loadSubTrie(entry, quitC) != nil { - return - } - entry.subtrie.deleteEntry(path[epl:], quitC) - entry.Hash = "" - // remove subtree if it has less than 2 elements - cnt, lastentry := entry.subtrie.getCountLast() - if cnt < 2 { - if lastentry != nil { - lastentry.Path = entry.Path + lastentry.Path - } - mt.entries[b] = lastentry - } - } -} - -func (mt *manifestTrie) recalcAndStore() error { - if mt.ref != nil { - return nil - } - - var buffer bytes.Buffer - buffer.WriteString(`{"entries":[`) - - list := &Manifest{} - for _, entry := range &mt.entries { - if entry != nil { - if entry.Hash == "" { // TODO: paralellize - err := entry.subtrie.recalcAndStore() - if err != nil { - return err - } - entry.Hash = entry.subtrie.ref.Hex() - } - list.Entries = append(list.Entries, entry.ManifestEntry) - } - - } - - manifest, err := json.Marshal(list) - if err != nil { - return err - } - - sr := bytes.NewReader(manifest) - ctx := context.TODO() - addr, wait, err2 := mt.fileStore.Store(ctx, sr, int64(len(manifest)), mt.encrypted) - if err2 != nil { - return err2 - } - err2 = wait(ctx) - mt.ref = addr - return err2 -} - -func (mt *manifestTrie) loadSubTrie(entry *manifestTrieEntry, quitC chan bool) (err error) { - if entry.ManifestEntry.Access != nil { - if mt.decrypt == nil { - return errors.New("dont have decryptor") - } - - err := mt.decrypt(&entry.ManifestEntry) - if err != nil { - return err - } - } - - if entry.subtrie == nil { - hash := common.Hex2Bytes(entry.Hash) - entry.subtrie, err = loadManifest(context.TODO(), mt.fileStore, hash, quitC, mt.decrypt) - entry.Hash = "" // might not match, should be recalculated - } - return -} - -func (mt *manifestTrie) listWithPrefixInt(prefix, rp string, quitC chan bool, cb func(entry *manifestTrieEntry, suffix string)) error { - plen := len(prefix) - var start, stop int - if plen == 0 { - start = 0 - stop = 256 - } else { - start = int(prefix[0]) - stop = start - } - - for i := start; i <= stop; i++ { - select { - case <-quitC: - return fmt.Errorf("aborted") - default: - } - entry := mt.entries[i] - if entry != nil { - epl := len(entry.Path) - if entry.ContentType == ManifestType { - l := plen - if epl < l { - l = epl - } - if prefix[:l] == entry.Path[:l] { - err := mt.loadSubTrie(entry, quitC) - if err != nil { - return err - } - err = entry.subtrie.listWithPrefixInt(prefix[l:], rp+entry.Path[l:], quitC, cb) - if err != nil { - return err - } - } - } else { - if (epl >= plen) && (prefix == entry.Path[:plen]) { - cb(entry, rp+entry.Path[plen:]) - } - } - } - } - return nil -} - -func (mt *manifestTrie) listWithPrefix(prefix string, quitC chan bool, cb func(entry *manifestTrieEntry, suffix string)) (err error) { - return mt.listWithPrefixInt(prefix, "", quitC, cb) -} - -func (mt *manifestTrie) findPrefixOf(path string, quitC chan bool) (entry *manifestTrieEntry, pos int) { - log.Trace(fmt.Sprintf("findPrefixOf(%s)", path)) - - if len(path) == 0 { - return mt.entries[256], 0 - } - - //see if first char is in manifest entries - b := path[0] - entry = mt.entries[b] - if entry == nil { - return mt.entries[256], 0 - } - - epl := len(entry.Path) - log.Trace(fmt.Sprintf("path = %v entry.Path = %v epl = %v", path, entry.Path, epl)) - if len(path) <= epl { - if entry.Path[:len(path)] == path { - if entry.ContentType == ManifestType { - err := mt.loadSubTrie(entry, quitC) - if err == nil && entry.subtrie != nil { - subentries := entry.subtrie.entries - for i := 0; i < len(subentries); i++ { - sub := subentries[i] - if sub != nil && sub.Path == "" { - return sub, len(path) - } - } - } - entry.Status = http.StatusMultipleChoices - } - pos = len(path) - return - } - return nil, 0 - } - if path[:epl] == entry.Path { - log.Trace(fmt.Sprintf("entry.ContentType = %v", entry.ContentType)) - //the subentry is a manifest, load subtrie - if entry.ContentType == ManifestType && (strings.Contains(entry.Path, path) || strings.Contains(path, entry.Path)) { - err := mt.loadSubTrie(entry, quitC) - if err != nil { - return nil, 0 - } - sub, pos := entry.subtrie.findPrefixOf(path[epl:], quitC) - if sub != nil { - entry = sub - pos += epl - return sub, pos - } else if path == entry.Path { - entry.Status = http.StatusMultipleChoices - } - - } else { - //entry is not a manifest, return it - if path != entry.Path { - return nil, 0 - } - } - } - return nil, 0 -} - -// file system manifest always contains regularized paths -// no leading or trailing slashes, only single slashes inside -func RegularSlashes(path string) (res string) { - for i := 0; i < len(path); i++ { - if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) { - res = res + path[i:i+1] - } - } - if (len(res) > 0) && (res[len(res)-1] == '/') { - res = res[:len(res)-1] - } - return -} - -func (mt *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) { - path := RegularSlashes(spath) - var pos int - quitC := make(chan bool) - entry, pos = mt.findPrefixOf(path, quitC) - return entry, path[:pos] -} diff --git a/swarm/api/manifest_test.go b/swarm/api/manifest_test.go deleted file mode 100644 index c193ebcb4..000000000 --- a/swarm/api/manifest_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "testing" - - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/storage" -) - -func manifest(paths ...string) (manifestReader storage.LazySectionReader) { - var entries []string - for _, path := range paths { - entry := fmt.Sprintf(`{"path":"%s"}`, path) - entries = append(entries, entry) - } - manifest := fmt.Sprintf(`{"entries":[%s]}`, strings.Join(entries, ",")) - return &storage.LazyTestSectionReader{ - SectionReader: io.NewSectionReader(strings.NewReader(manifest), 0, int64(len(manifest))), - } -} - -func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...string) *manifestTrie { - quitC := make(chan bool) - fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams(), chunk.NewTags()) - ref := make([]byte, fileStore.HashSize()) - trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC, NOOPDecrypt) - if err != nil { - t.Errorf("unexpected error making manifest: %v", err) - } - checkEntry(t, path, match, multiple, trie) - return trie -} - -func checkEntry(t *testing.T, path, match string, multiple bool, trie *manifestTrie) { - entry, fullpath := trie.getEntry(path) - if match == "-" && entry != nil { - t.Errorf("expected no match for '%s', got '%s'", path, fullpath) - } else if entry == nil { - if match != "-" { - t.Errorf("expected entry '%s' to match '%s', got no match", match, path) - } - } else if fullpath != match { - t.Errorf("incorrect entry retrieved for '%s'. expected path '%v', got '%s'", path, match, fullpath) - } - - if multiple && entry.Status != http.StatusMultipleChoices { - t.Errorf("Expected %d Multiple Choices Status for path %s, match %s, got %d", http.StatusMultipleChoices, path, match, entry.Status) - } else if !multiple && entry != nil && entry.Status == http.StatusMultipleChoices { - t.Errorf("Were not expecting %d Multiple Choices Status for path %s, match %s, but got it", http.StatusMultipleChoices, path, match) - } -} - -func TestGetEntry(t *testing.T) { - // file system manifest always contains regularized paths - testGetEntry(t, "a", "a", false, "a") - testGetEntry(t, "b", "-", false, "a") - testGetEntry(t, "/a//", "a", false, "a") - // fallback - testGetEntry(t, "/a", "", false, "") - testGetEntry(t, "/a/b", "a/b", false, "a/b") - // longest/deepest math - testGetEntry(t, "read", "read", true, "readme.md", "readit.md") - testGetEntry(t, "rf", "-", false, "readme.md", "readit.md") - testGetEntry(t, "readme", "readme", false, "readme.md") - testGetEntry(t, "readme", "-", false, "readit.md") - testGetEntry(t, "readme.md", "readme.md", false, "readme.md") - testGetEntry(t, "readme.md", "-", false, "readit.md") - testGetEntry(t, "readmeAmd", "-", false, "readit.md") - testGetEntry(t, "readme.mdffff", "-", false, "readme.md") - testGetEntry(t, "ab", "ab", true, "ab/cefg", "ab/cedh", "ab/kkkkkk") - testGetEntry(t, "ab/ce", "ab/ce", true, "ab/cefg", "ab/cedh", "ab/ceuuuuuuuuuu") - testGetEntry(t, "abc", "abc", true, "abcd", "abczzzzef", "abc/def", "abc/e/g") - testGetEntry(t, "a/b", "a/b", true, "a", "a/bc", "a/ba", "a/b/c") - testGetEntry(t, "a/b", "a/b", false, "a", "a/b", "a/bb", "a/b/c") - testGetEntry(t, "//a//b//", "a/b", false, "a", "a/b", "a/bb", "a/b/c") -} - -func TestExactMatch(t *testing.T) { - quitC := make(chan bool) - mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map") - fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams(), chunk.NewTags()) - ref := make([]byte, fileStore.HashSize()) - trie, err := readManifest(mf, ref, fileStore, false, quitC, nil) - if err != nil { - t.Errorf("unexpected error making manifest: %v", err) - } - entry, _ := trie.getEntry("shouldBeExactMatch.css") - if entry.Path != "" { - t.Errorf("Expected entry to match %s, got: %s", "shouldBeExactMatch.css", entry.Path) - } - if entry.Status == http.StatusMultipleChoices { - t.Errorf("Got status %d, which is unexepcted", http.StatusMultipleChoices) - } -} - -func TestDeleteEntry(t *testing.T) { - -} - -// TestAddFileWithManifestPath tests that adding an entry at a path which -// already exists as a manifest just adds the entry to the manifest rather -// than replacing the manifest with the entry -func TestAddFileWithManifestPath(t *testing.T) { - // create a manifest containing "ab" and "ac" - manifest, _ := json.Marshal(&Manifest{ - Entries: []ManifestEntry{ - {Path: "ab", Hash: "ab"}, - {Path: "ac", Hash: "ac"}, - }, - }) - reader := &storage.LazyTestSectionReader{ - SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))), - } - fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams(), chunk.NewTags()) - ref := make([]byte, fileStore.HashSize()) - trie, err := readManifest(reader, ref, fileStore, false, nil, NOOPDecrypt) - if err != nil { - t.Fatal(err) - } - checkEntry(t, "ab", "ab", false, trie) - checkEntry(t, "ac", "ac", false, trie) - - // now add path "a" and check we can still get "ab" and "ac" - entry := &manifestTrieEntry{} - entry.Path = "a" - entry.Hash = "a" - trie.addEntry(entry, nil) - checkEntry(t, "ab", "ab", false, trie) - checkEntry(t, "ac", "ac", false, trie) - checkEntry(t, "a", "a", false, trie) -} - -// TestReadManifestOverSizeLimit creates a manifest reader with data longer then -// manifestSizeLimit and checks if readManifest function will return the exact error -// message. -// The manifest data is not in json-encoded format, preventing possbile -// successful parsing attempts if limit check fails. -func TestReadManifestOverSizeLimit(t *testing.T) { - manifest := make([]byte, manifestSizeLimit+1) - reader := &storage.LazyTestSectionReader{ - SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))), - } - _, err := readManifest(reader, storage.Address{}, nil, false, nil, NOOPDecrypt) - if err == nil { - t.Fatal("got no error from readManifest") - } - // Error message is part of the http response body - // which justifies exact string validation. - got := err.Error() - want := fmt.Sprintf("Manifest size of %v bytes exceeds the %v byte limit", len(manifest), manifestSizeLimit) - if got != want { - t.Fatalf("got error mesage %q, expected %q", got, want) - } -} diff --git a/swarm/api/testdata/test0/img/logo.png b/swarm/api/testdata/test0/img/logo.png Binary files differdeleted file mode 100644 index 9557f9605..000000000 --- a/swarm/api/testdata/test0/img/logo.png +++ /dev/null diff --git a/swarm/api/testdata/test0/index.css b/swarm/api/testdata/test0/index.css deleted file mode 100644 index 693b13a37..000000000 --- a/swarm/api/testdata/test0/index.css +++ /dev/null @@ -1,9 +0,0 @@ -h1 { - color: black; - font-size: 12px; - background-color: orange; - border: 4px solid black; -} -body { - background-color: orange -} diff --git a/swarm/api/testdata/test0/index.html b/swarm/api/testdata/test0/index.html deleted file mode 100644 index 321e910d7..000000000 --- a/swarm/api/testdata/test0/index.html +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <link rel="stylesheet" href="index.css"> - </head> - <body> - <h1>Swarm Test</h1> - <img src="img/logo.gif" align="center", alt="Ethereum logo"> - </body> -</html>
\ No newline at end of file diff --git a/swarm/api/uri.go b/swarm/api/uri.go deleted file mode 100644 index 09cfa4502..000000000 --- a/swarm/api/uri.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "fmt" - "net/url" - "regexp" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/swarm/storage" -) - -//matches hex swarm hashes -// TODO: this is bad, it should not be hardcoded how long is a hash -var hashMatcher = regexp.MustCompile("^([0-9A-Fa-f]{64})([0-9A-Fa-f]{64})?$") - -// URI is a reference to content stored in swarm. -type URI struct { - // Scheme has one of the following values: - // - // * bzz - an entry in a swarm manifest - // * bzz-raw - raw swarm content - // * bzz-immutable - immutable URI of an entry in a swarm manifest - // (address is not resolved) - // * bzz-list - list of all files contained in a swarm manifest - // - Scheme string - - // Addr is either a hexadecimal storage address or it an address which - // resolves to a storage address - Addr string - - // addr stores the parsed storage address - addr storage.Address - - // Path is the path to the content within a swarm manifest - Path string -} - -func (u *URI) MarshalJSON() (out []byte, err error) { - return []byte(`"` + u.String() + `"`), nil -} - -func (u *URI) UnmarshalJSON(value []byte) error { - uri, err := Parse(string(value)) - if err != nil { - return err - } - *u = *uri - return nil -} - -// Parse parses rawuri into a URI struct, where rawuri is expected to have one -// of the following formats: -// -// * <scheme>:/ -// * <scheme>:/<addr> -// * <scheme>:/<addr>/<path> -// * <scheme>:// -// * <scheme>://<addr> -// * <scheme>://<addr>/<path> -// -// with scheme one of bzz, bzz-raw, bzz-immutable, bzz-list or bzz-hash -func Parse(rawuri string) (*URI, error) { - u, err := url.Parse(rawuri) - if err != nil { - return nil, err - } - uri := &URI{Scheme: u.Scheme} - - // check the scheme is valid - switch uri.Scheme { - case "bzz", "bzz-raw", "bzz-immutable", "bzz-list", "bzz-hash", "bzz-feed": - default: - return nil, fmt.Errorf("unknown scheme %q", u.Scheme) - } - - // handle URIs like bzz://<addr>/<path> where the addr and path - // have already been split by url.Parse - if u.Host != "" { - uri.Addr = u.Host - uri.Path = strings.TrimLeft(u.Path, "/") - return uri, nil - } - - // URI is like bzz:/<addr>/<path> so split the addr and path from - // the raw path (which will be /<addr>/<path>) - parts := strings.SplitN(strings.TrimLeft(u.Path, "/"), "/", 2) - uri.Addr = parts[0] - if len(parts) == 2 { - uri.Path = parts[1] - } - return uri, nil -} -func (u *URI) Feed() bool { - return u.Scheme == "bzz-feed" -} - -func (u *URI) Raw() bool { - return u.Scheme == "bzz-raw" -} - -func (u *URI) Immutable() bool { - return u.Scheme == "bzz-immutable" -} - -func (u *URI) List() bool { - return u.Scheme == "bzz-list" -} - -func (u *URI) Hash() bool { - return u.Scheme == "bzz-hash" -} - -func (u *URI) String() string { - return u.Scheme + ":/" + u.Addr + "/" + u.Path -} - -func (u *URI) Address() storage.Address { - if u.addr != nil { - return u.addr - } - if hashMatcher.MatchString(u.Addr) { - u.addr = common.Hex2Bytes(u.Addr) - return u.addr - } - return nil -} diff --git a/swarm/api/uri_test.go b/swarm/api/uri_test.go deleted file mode 100644 index a03874c43..000000000 --- a/swarm/api/uri_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package api - -import ( - "bytes" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/swarm/storage" -) - -func TestParseURI(t *testing.T) { - type test struct { - uri string - expectURI *URI - expectErr bool - expectRaw bool - expectImmutable bool - expectList bool - expectHash bool - expectValidKey bool - expectAddr storage.Address - } - tests := []test{ - { - uri: "", - expectErr: true, - }, - { - uri: "foo", - expectErr: true, - }, - { - uri: "bzz", - expectErr: true, - }, - { - uri: "bzz:", - expectURI: &URI{Scheme: "bzz"}, - }, - { - uri: "bzz-immutable:", - expectURI: &URI{Scheme: "bzz-immutable"}, - expectImmutable: true, - }, - { - uri: "bzz-raw:", - expectURI: &URI{Scheme: "bzz-raw"}, - expectRaw: true, - }, - { - uri: "bzz:/", - expectURI: &URI{Scheme: "bzz"}, - }, - { - uri: "bzz:/abc123", - expectURI: &URI{Scheme: "bzz", Addr: "abc123"}, - }, - { - uri: "bzz:/abc123/path/to/entry", - expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, - }, - { - uri: "bzz-raw:/", - expectURI: &URI{Scheme: "bzz-raw"}, - expectRaw: true, - }, - { - uri: "bzz-raw:/abc123", - expectURI: &URI{Scheme: "bzz-raw", Addr: "abc123"}, - expectRaw: true, - }, - { - uri: "bzz-raw:/abc123/path/to/entry", - expectURI: &URI{Scheme: "bzz-raw", Addr: "abc123", Path: "path/to/entry"}, - expectRaw: true, - }, - { - uri: "bzz://", - expectURI: &URI{Scheme: "bzz"}, - }, - { - uri: "bzz://abc123", - expectURI: &URI{Scheme: "bzz", Addr: "abc123"}, - }, - { - uri: "bzz://abc123/path/to/entry", - expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, - }, - { - uri: "bzz-hash:", - expectURI: &URI{Scheme: "bzz-hash"}, - expectHash: true, - }, - { - uri: "bzz-hash:/", - expectURI: &URI{Scheme: "bzz-hash"}, - expectHash: true, - }, - { - uri: "bzz-list:", - expectURI: &URI{Scheme: "bzz-list"}, - expectList: true, - }, - { - uri: "bzz-list:/", - expectURI: &URI{Scheme: "bzz-list"}, - expectList: true, - }, - { - uri: "bzz-raw://4378d19c26590f1a818ed7d6a62c3809e149b0999cab5ce5f26233b3b423bf8c", - expectURI: &URI{Scheme: "bzz-raw", - Addr: "4378d19c26590f1a818ed7d6a62c3809e149b0999cab5ce5f26233b3b423bf8c", - }, - expectValidKey: true, - expectRaw: true, - expectAddr: storage.Address{67, 120, 209, 156, 38, 89, 15, 26, - 129, 142, 215, 214, 166, 44, 56, 9, - 225, 73, 176, 153, 156, 171, 92, 229, - 242, 98, 51, 179, 180, 35, 191, 140, - }, - }, - } - for _, x := range tests { - actual, err := Parse(x.uri) - if x.expectErr { - if err == nil { - t.Fatalf("expected %s to error", x.uri) - } - continue - } - if err != nil { - t.Fatalf("error parsing %s: %s", x.uri, err) - } - if !reflect.DeepEqual(actual, x.expectURI) { - t.Fatalf("expected %s to return %#v, got %#v", x.uri, x.expectURI, actual) - } - if actual.Raw() != x.expectRaw { - t.Fatalf("expected %s raw to be %t, got %t", x.uri, x.expectRaw, actual.Raw()) - } - if actual.Immutable() != x.expectImmutable { - t.Fatalf("expected %s immutable to be %t, got %t", x.uri, x.expectImmutable, actual.Immutable()) - } - if actual.List() != x.expectList { - t.Fatalf("expected %s list to be %t, got %t", x.uri, x.expectList, actual.List()) - } - if actual.Hash() != x.expectHash { - t.Fatalf("expected %s hash to be %t, got %t", x.uri, x.expectHash, actual.Hash()) - } - if x.expectValidKey { - if actual.Address() == nil { - t.Fatalf("expected %s to return a valid key, got nil", x.uri) - } else { - if !bytes.Equal(x.expectAddr, actual.Address()) { - t.Fatalf("expected %s to be decoded to %v", x.expectURI.Addr, x.expectAddr) - } - } - } - } -} |