aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/api
diff options
context:
space:
mode:
Diffstat (limited to 'swarm/api')
-rw-r--r--swarm/api/act.go538
-rw-r--r--swarm/api/api.go993
-rw-r--r--swarm/api/api_test.go576
-rw-r--r--swarm/api/client/client.go829
-rw-r--r--swarm/api/client/client_test.go608
-rw-r--r--swarm/api/config.go174
-rw-r--r--swarm/api/config_test.go66
-rw-r--r--swarm/api/encrypt.go78
-rw-r--r--swarm/api/filesystem.go292
-rw-r--r--swarm/api/filesystem_test.go200
-rw-r--r--swarm/api/gen_mime.go1201
-rw-r--r--swarm/api/http/middleware.go162
-rw-r--r--swarm/api/http/response.go132
-rw-r--r--swarm/api/http/response_test.go170
-rw-r--r--swarm/api/http/roundtripper.go66
-rw-r--r--swarm/api/http/roundtripper_test.go69
-rw-r--r--swarm/api/http/sctx.go34
-rw-r--r--swarm/api/http/server.go937
-rw-r--r--swarm/api/http/server_test.go1409
-rw-r--r--swarm/api/http/templates.go306
-rw-r--r--swarm/api/http/test_server.go100
-rw-r--r--swarm/api/inspector.go84
-rw-r--r--swarm/api/manifest.go584
-rw-r--r--swarm/api/manifest_test.go176
-rw-r--r--swarm/api/testdata/test0/img/logo.pngbin4119 -> 0 bytes
-rw-r--r--swarm/api/testdata/test0/index.css9
-rw-r--r--swarm/api/testdata/test0/index.html10
-rw-r--r--swarm/api/uri.go144
-rw-r--r--swarm/api/uri_test.go175
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 &#34;/this_should_fail_as_no_bzz_protocol_present&#34;: 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
deleted file mode 100644
index 9557f9605..000000000
--- a/swarm/api/testdata/test0/img/logo.png
+++ /dev/null
Binary files differ
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)
- }
- }
- }
- }
-}