github.com/ethersphere/bee/v2@v2.2.0/pkg/api/api.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package api provides the functionality of the Bee
     6  // client-facing HTTP API.
     7  package api
     8  
     9  import (
    10  	"context"
    11  	"crypto/ecdsa"
    12  	"encoding/base64"
    13  	"encoding/hex"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"math"
    18  	"math/big"
    19  	"mime"
    20  	"net/http"
    21  	"reflect"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  	"unicode/utf8"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethersphere/bee/v2/pkg/accesscontrol"
    30  	"github.com/ethersphere/bee/v2/pkg/accounting"
    31  	"github.com/ethersphere/bee/v2/pkg/crypto"
    32  	"github.com/ethersphere/bee/v2/pkg/feeds"
    33  	"github.com/ethersphere/bee/v2/pkg/file/pipeline"
    34  	"github.com/ethersphere/bee/v2/pkg/file/pipeline/builder"
    35  	"github.com/ethersphere/bee/v2/pkg/file/redundancy"
    36  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    37  	"github.com/ethersphere/bee/v2/pkg/log"
    38  	"github.com/ethersphere/bee/v2/pkg/p2p"
    39  	"github.com/ethersphere/bee/v2/pkg/pingpong"
    40  	"github.com/ethersphere/bee/v2/pkg/postage"
    41  	"github.com/ethersphere/bee/v2/pkg/postage/postagecontract"
    42  	"github.com/ethersphere/bee/v2/pkg/pss"
    43  	"github.com/ethersphere/bee/v2/pkg/resolver"
    44  	"github.com/ethersphere/bee/v2/pkg/resolver/client/ens"
    45  	"github.com/ethersphere/bee/v2/pkg/sctx"
    46  	"github.com/ethersphere/bee/v2/pkg/settlement"
    47  	"github.com/ethersphere/bee/v2/pkg/settlement/swap"
    48  	"github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook"
    49  	"github.com/ethersphere/bee/v2/pkg/settlement/swap/erc20"
    50  	"github.com/ethersphere/bee/v2/pkg/status"
    51  	"github.com/ethersphere/bee/v2/pkg/steward"
    52  	storage "github.com/ethersphere/bee/v2/pkg/storage"
    53  	"github.com/ethersphere/bee/v2/pkg/storageincentives"
    54  	"github.com/ethersphere/bee/v2/pkg/storageincentives/staking"
    55  	storer "github.com/ethersphere/bee/v2/pkg/storer"
    56  	"github.com/ethersphere/bee/v2/pkg/swarm"
    57  	"github.com/ethersphere/bee/v2/pkg/topology"
    58  	"github.com/ethersphere/bee/v2/pkg/topology/lightnode"
    59  	"github.com/ethersphere/bee/v2/pkg/tracing"
    60  	"github.com/ethersphere/bee/v2/pkg/transaction"
    61  	"github.com/go-playground/validator/v10"
    62  	"github.com/gorilla/mux"
    63  	"github.com/hashicorp/go-multierror"
    64  	"github.com/prometheus/client_golang/prometheus"
    65  	"golang.org/x/sync/semaphore"
    66  )
    67  
    68  // loggerName is the tree path name of the logger for this package.
    69  const loggerName = "api"
    70  
    71  const (
    72  	SwarmPinHeader                    = "Swarm-Pin"
    73  	SwarmTagHeader                    = "Swarm-Tag"
    74  	SwarmEncryptHeader                = "Swarm-Encrypt"
    75  	SwarmIndexDocumentHeader          = "Swarm-Index-Document"
    76  	SwarmErrorDocumentHeader          = "Swarm-Error-Document"
    77  	SwarmFeedIndexHeader              = "Swarm-Feed-Index"
    78  	SwarmFeedIndexNextHeader          = "Swarm-Feed-Index-Next"
    79  	SwarmCollectionHeader             = "Swarm-Collection"
    80  	SwarmPostageBatchIdHeader         = "Swarm-Postage-Batch-Id"
    81  	SwarmPostageStampHeader           = "Swarm-Postage-Stamp"
    82  	SwarmDeferredUploadHeader         = "Swarm-Deferred-Upload"
    83  	SwarmRedundancyLevelHeader        = "Swarm-Redundancy-Level"
    84  	SwarmRedundancyStrategyHeader     = "Swarm-Redundancy-Strategy"
    85  	SwarmRedundancyFallbackModeHeader = "Swarm-Redundancy-Fallback-Mode"
    86  	SwarmChunkRetrievalTimeoutHeader  = "Swarm-Chunk-Retrieval-Timeout"
    87  	SwarmLookAheadBufferSizeHeader    = "Swarm-Lookahead-Buffer-Size"
    88  	SwarmActHeader                    = "Swarm-Act"
    89  	SwarmActTimestampHeader           = "Swarm-Act-Timestamp"
    90  	SwarmActPublisherHeader           = "Swarm-Act-Publisher"
    91  	SwarmActHistoryAddressHeader      = "Swarm-Act-History-Address"
    92  
    93  	ImmutableHeader = "Immutable"
    94  	GasPriceHeader  = "Gas-Price"
    95  	GasLimitHeader  = "Gas-Limit"
    96  	ETagHeader      = "ETag"
    97  
    98  	AuthorizationHeader      = "Authorization"
    99  	AcceptEncodingHeader     = "Accept-Encoding"
   100  	ContentTypeHeader        = "Content-Type"
   101  	ContentDispositionHeader = "Content-Disposition"
   102  	ContentLengthHeader      = "Content-Length"
   103  	RangeHeader              = "Range"
   104  	OriginHeader             = "Origin"
   105  )
   106  
   107  const (
   108  	multiPartFormData  = "multipart/form-data"
   109  	contentTypeTar     = "application/x-tar"
   110  	boolHeaderSetValue = "true"
   111  )
   112  
   113  var (
   114  	errInvalidNameOrAddress             = errors.New("invalid name or bzz address")
   115  	errNoResolver                       = errors.New("no resolver connected")
   116  	errInvalidRequest                   = errors.New("could not validate request")
   117  	errInvalidContentType               = errors.New("invalid content-type")
   118  	errDirectoryStore                   = errors.New("could not store directory")
   119  	errFileStore                        = errors.New("could not store file")
   120  	errInvalidPostageBatch              = errors.New("invalid postage batch id")
   121  	errBatchUnusable                    = errors.New("batch not usable")
   122  	errUnsupportedDevNodeOperation      = errors.New("operation not supported in dev mode")
   123  	errOperationSupportedOnlyInFullMode = errors.New("operation is supported only in full mode")
   124  	errActDownload                      = errors.New("act download failed")
   125  	errActUpload                        = errors.New("act upload failed")
   126  	errActGranteeList                   = errors.New("failed to create or update grantee list")
   127  
   128  	batchIdOrStampSig = fmt.Sprintf("Either '%s' or '%s' header must be set in the request", SwarmPostageStampHeader, SwarmPostageBatchIdHeader)
   129  )
   130  
   131  // Storer interface provides the functionality required from the local storage
   132  // component of the node.
   133  type Storer interface {
   134  	storer.UploadStore
   135  	storer.PinStore
   136  	storer.CacheStore
   137  	storer.NetStore
   138  	storer.LocalStore
   139  	storer.RadiusChecker
   140  	storer.Debugger
   141  }
   142  
   143  type PinIntegrity interface {
   144  	Check(ctx context.Context, logger log.Logger, pin string, out chan storer.PinStat)
   145  }
   146  
   147  type Service struct {
   148  	storer          Storer
   149  	resolver        resolver.Interface
   150  	pss             pss.Interface
   151  	steward         steward.Interface
   152  	logger          log.Logger
   153  	loggerV1        log.Logger
   154  	tracer          *tracing.Tracer
   155  	feedFactory     feeds.Factory
   156  	signer          crypto.Signer
   157  	post            postage.Service
   158  	accesscontrol   accesscontrol.Controller
   159  	postageContract postagecontract.Interface
   160  	probe           *Probe
   161  	metricsRegistry *prometheus.Registry
   162  	stakingContract staking.Contract
   163  	Options
   164  
   165  	http.Handler
   166  	router *mux.Router
   167  
   168  	metrics metrics
   169  
   170  	wsWg sync.WaitGroup // wait for all websockets to close on exit
   171  	quit chan struct{}
   172  
   173  	overlay           *swarm.Address
   174  	publicKey         ecdsa.PublicKey
   175  	pssPublicKey      ecdsa.PublicKey
   176  	ethereumAddress   common.Address
   177  	chequebookEnabled bool
   178  	swapEnabled       bool
   179  
   180  	topologyDriver topology.Driver
   181  	p2p            p2p.DebugService
   182  	accounting     accounting.Interface
   183  	chequebook     chequebook.Service
   184  	pseudosettle   settlement.Interface
   185  	pingpong       pingpong.Interface
   186  
   187  	batchStore   postage.Storer
   188  	stamperStore storage.Store
   189  	pinIntegrity PinIntegrity
   190  
   191  	syncStatus func() (bool, error)
   192  
   193  	swap        swap.Interface
   194  	transaction transaction.Service
   195  	lightNodes  *lightnode.Container
   196  	blockTime   time.Duration
   197  
   198  	statusSem        *semaphore.Weighted
   199  	postageSem       *semaphore.Weighted
   200  	stakingSem       *semaphore.Weighted
   201  	cashOutChequeSem *semaphore.Weighted
   202  	beeMode          BeeNodeMode
   203  
   204  	chainBackend transaction.Backend
   205  	erc20Service erc20.Service
   206  	chainID      int64
   207  
   208  	whitelistedWithdrawalAddress []common.Address
   209  
   210  	preMapHooks map[string]func(v string) (string, error)
   211  	validate    *validator.Validate
   212  
   213  	redistributionAgent *storageincentives.Agent
   214  
   215  	statusService *status.Service
   216  }
   217  
   218  func (s *Service) SetP2P(p2p p2p.DebugService) {
   219  	if s != nil {
   220  		s.p2p = p2p
   221  	}
   222  }
   223  
   224  func (s *Service) SetSwarmAddress(addr *swarm.Address) {
   225  	if s != nil {
   226  		s.overlay = addr
   227  	}
   228  }
   229  
   230  func (s *Service) SetRedistributionAgent(redistributionAgent *storageincentives.Agent) {
   231  	if s != nil {
   232  		s.redistributionAgent = redistributionAgent
   233  	}
   234  }
   235  
   236  type Options struct {
   237  	CORSAllowedOrigins []string
   238  	WsPingPeriod       time.Duration
   239  }
   240  
   241  type ExtraOptions struct {
   242  	Pingpong        pingpong.Interface
   243  	TopologyDriver  topology.Driver
   244  	LightNodes      *lightnode.Container
   245  	Accounting      accounting.Interface
   246  	Pseudosettle    settlement.Interface
   247  	Swap            swap.Interface
   248  	Chequebook      chequebook.Service
   249  	BlockTime       time.Duration
   250  	Storer          Storer
   251  	Resolver        resolver.Interface
   252  	Pss             pss.Interface
   253  	FeedFactory     feeds.Factory
   254  	Post            postage.Service
   255  	AccessControl   accesscontrol.Controller
   256  	PostageContract postagecontract.Interface
   257  	Staking         staking.Contract
   258  	Steward         steward.Interface
   259  	SyncStatus      func() (bool, error)
   260  	NodeStatus      *status.Service
   261  	PinIntegrity    PinIntegrity
   262  }
   263  
   264  func New(
   265  	publicKey, pssPublicKey ecdsa.PublicKey,
   266  	ethereumAddress common.Address,
   267  	whitelistedWithdrawalAddress []string,
   268  	logger log.Logger,
   269  	transaction transaction.Service,
   270  	batchStore postage.Storer,
   271  	beeMode BeeNodeMode,
   272  	chequebookEnabled bool,
   273  	swapEnabled bool,
   274  	chainBackend transaction.Backend,
   275  	cors []string,
   276  	stamperStore storage.Store,
   277  ) *Service {
   278  	s := new(Service)
   279  
   280  	s.CORSAllowedOrigins = cors
   281  	s.beeMode = beeMode
   282  	s.logger = logger.WithName(loggerName).Register()
   283  	s.loggerV1 = s.logger.V(1).Register()
   284  	s.chequebookEnabled = chequebookEnabled
   285  	s.swapEnabled = swapEnabled
   286  	s.publicKey = publicKey
   287  	s.pssPublicKey = pssPublicKey
   288  	s.ethereumAddress = ethereumAddress
   289  	s.transaction = transaction
   290  	s.batchStore = batchStore
   291  	s.chainBackend = chainBackend
   292  	s.metricsRegistry = newDebugMetrics()
   293  	s.preMapHooks = map[string]func(v string) (string, error){
   294  		"mimeMediaType": func(v string) (string, error) {
   295  			typ, _, err := mime.ParseMediaType(v)
   296  			return typ, err
   297  		},
   298  		"decBase64url": func(v string) (string, error) {
   299  			buf, err := base64.URLEncoding.DecodeString(v)
   300  			return string(buf), err
   301  		},
   302  		"decHex": func(v string) (string, error) {
   303  			buf, err := hex.DecodeString(v)
   304  			return string(buf), err
   305  		},
   306  	}
   307  	s.validate = validator.New()
   308  	s.validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
   309  		name := strings.SplitN(fld.Tag.Get(mapStructureTagName), ",", 2)[0]
   310  		if name == "-" {
   311  			return ""
   312  		}
   313  		return name
   314  	})
   315  	s.stamperStore = stamperStore
   316  
   317  	for _, v := range whitelistedWithdrawalAddress {
   318  		s.whitelistedWithdrawalAddress = append(s.whitelistedWithdrawalAddress, common.HexToAddress(v))
   319  	}
   320  
   321  	return s
   322  }
   323  
   324  // Configure will create a and initialize a new API service.
   325  func (s *Service) Configure(signer crypto.Signer, tracer *tracing.Tracer, o Options, e ExtraOptions, chainID int64, erc20 erc20.Service) {
   326  	s.signer = signer
   327  	s.Options = o
   328  	s.tracer = tracer
   329  	s.metrics = newMetrics()
   330  
   331  	s.quit = make(chan struct{})
   332  
   333  	s.storer = e.Storer
   334  	s.resolver = e.Resolver
   335  	s.pss = e.Pss
   336  	s.feedFactory = e.FeedFactory
   337  	s.post = e.Post
   338  	s.accesscontrol = e.AccessControl
   339  	s.postageContract = e.PostageContract
   340  	s.steward = e.Steward
   341  	s.stakingContract = e.Staking
   342  
   343  	s.pingpong = e.Pingpong
   344  	s.topologyDriver = e.TopologyDriver
   345  	s.accounting = e.Accounting
   346  	s.chequebook = e.Chequebook
   347  	s.swap = e.Swap
   348  	s.lightNodes = e.LightNodes
   349  	s.pseudosettle = e.Pseudosettle
   350  	s.blockTime = e.BlockTime
   351  
   352  	s.statusSem = semaphore.NewWeighted(1)
   353  	s.postageSem = semaphore.NewWeighted(1)
   354  	s.stakingSem = semaphore.NewWeighted(1)
   355  	s.cashOutChequeSem = semaphore.NewWeighted(1)
   356  
   357  	s.chainID = chainID
   358  	s.erc20Service = erc20
   359  	s.syncStatus = e.SyncStatus
   360  
   361  	s.statusService = e.NodeStatus
   362  
   363  	s.preMapHooks["resolve"] = func(v string) (string, error) {
   364  		switch addr, err := s.resolveNameOrAddress(v); {
   365  		case err == nil:
   366  			return addr.String(), nil
   367  		case errors.Is(err, ens.ErrNotImplemented):
   368  			return v, nil
   369  		default:
   370  			return "", err
   371  		}
   372  	}
   373  
   374  	s.pinIntegrity = e.PinIntegrity
   375  }
   376  
   377  func (s *Service) SetProbe(probe *Probe) {
   378  	s.probe = probe
   379  }
   380  
   381  // Close hangs up running websockets on shutdown.
   382  func (s *Service) Close() error {
   383  	s.logger.Info("api shutting down")
   384  	close(s.quit)
   385  
   386  	done := make(chan struct{})
   387  	go func() {
   388  		defer close(done)
   389  		s.wsWg.Wait()
   390  	}()
   391  
   392  	select {
   393  	case <-done:
   394  	case <-time.After(1 * time.Second):
   395  		return errors.New("api shutting down with open websockets")
   396  	}
   397  
   398  	return nil
   399  }
   400  
   401  // getOrCreateSessionID attempts to get the session if an tag id is supplied, and returns an error
   402  // if it does not exist. If no id is supplied, it will attempt to create a new session and return it.
   403  func (s *Service) getOrCreateSessionID(tagUid uint64) (uint64, error) {
   404  	var (
   405  		tag storer.SessionInfo
   406  		err error
   407  	)
   408  	// if tag ID is not supplied, create a new tag
   409  	if tagUid == 0 {
   410  		tag, err = s.storer.NewSession()
   411  	} else {
   412  		tag, err = s.storer.Session(tagUid)
   413  	}
   414  	return tag.TagID, err
   415  }
   416  
   417  func (s *Service) resolveNameOrAddress(str string) (swarm.Address, error) {
   418  	// Try and mapStructure the name as a bzz address.
   419  	addr, err := swarm.ParseHexAddress(str)
   420  	if err == nil {
   421  		s.loggerV1.Debug("resolve name: parsing bzz address successful", "string", str, "address", addr)
   422  		return addr, nil
   423  	}
   424  
   425  	// If no resolver is not available, return an error.
   426  	if s.resolver == nil {
   427  		return swarm.ZeroAddress, errNoResolver
   428  	}
   429  
   430  	// Try and resolve the name using the provided resolver.
   431  	s.logger.Debug("resolve name: attempting to resolve string to address", "string", str)
   432  	addr, err = s.resolver.Resolve(str)
   433  	if err == nil {
   434  		s.loggerV1.Debug("resolve name: address resolved successfully", "string", str, "address", addr)
   435  		return addr, nil
   436  	}
   437  
   438  	return swarm.ZeroAddress, fmt.Errorf("%w: %w", errInvalidNameOrAddress, err)
   439  }
   440  
   441  func (s *Service) newTracingHandler(spanName string) func(h http.Handler) http.Handler {
   442  	return func(h http.Handler) http.Handler {
   443  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   444  			ctx, err := s.tracer.WithContextFromHTTPHeaders(r.Context(), r.Header)
   445  			if err != nil && !errors.Is(err, tracing.ErrContextNotFound) {
   446  				s.logger.Debug("extract tracing context failed", "span_name", spanName, "error", err)
   447  				// ignore
   448  			}
   449  
   450  			span, _, ctx := s.tracer.StartSpanFromContext(ctx, spanName, s.logger)
   451  			defer span.Finish()
   452  
   453  			err = s.tracer.AddContextHTTPHeader(ctx, r.Header)
   454  			if err != nil {
   455  				s.logger.Debug("inject tracing context failed", "span_name", spanName, "error", err)
   456  				// ignore
   457  			}
   458  
   459  			h.ServeHTTP(w, r.WithContext(ctx))
   460  		})
   461  	}
   462  }
   463  
   464  func (s *Service) contentLengthMetricMiddleware() func(h http.Handler) http.Handler {
   465  	return func(h http.Handler) http.Handler {
   466  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   467  			now := time.Now()
   468  			h.ServeHTTP(w, r)
   469  			switch r.Method {
   470  			case http.MethodGet:
   471  				hdr := w.Header().Get(ContentLengthHeader)
   472  				if hdr == "" {
   473  					s.logger.Debug("content length header not found")
   474  					return
   475  				}
   476  
   477  				contentLength, err := strconv.Atoi(hdr)
   478  				if err != nil {
   479  					s.logger.Debug("int conversion failed", "content_length", hdr, "error", err)
   480  					return
   481  				}
   482  				if contentLength > 0 {
   483  					s.metrics.ContentApiDuration.WithLabelValues(strconv.FormatInt(toFileSizeBucket(int64(contentLength)), 10), r.Method).Observe(time.Since(now).Seconds())
   484  				}
   485  			case http.MethodPost:
   486  				if r.ContentLength > 0 {
   487  					s.metrics.ContentApiDuration.WithLabelValues(strconv.FormatInt(toFileSizeBucket(r.ContentLength), 10), r.Method).Observe(time.Since(now).Seconds())
   488  				}
   489  			}
   490  		})
   491  	}
   492  }
   493  
   494  // gasConfigMiddleware can be used by the APIs that allow block chain transactions to set
   495  // gas price and gas limit through the HTTP API headers.
   496  func (s *Service) gasConfigMiddleware(handlerName string) func(h http.Handler) http.Handler {
   497  	return func(h http.Handler) http.Handler {
   498  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   499  			logger := s.logger.WithName(handlerName).Build()
   500  
   501  			headers := struct {
   502  				GasPrice *big.Int `map:"Gas-Price"`
   503  				GasLimit uint64   `map:"Gas-Limit"`
   504  			}{}
   505  			if response := s.mapStructure(r.Header, &headers); response != nil {
   506  				response("invalid header params", logger, w)
   507  				return
   508  			}
   509  			ctx := r.Context()
   510  			ctx = sctx.SetGasPrice(ctx, headers.GasPrice)
   511  			ctx = sctx.SetGasLimit(ctx, headers.GasLimit)
   512  
   513  			h.ServeHTTP(w, r.WithContext(ctx))
   514  		})
   515  	}
   516  }
   517  
   518  // corsHandler sets CORS headers to HTTP response if allowed origins are configured.
   519  func (s *Service) corsHandler(h http.Handler) http.Handler {
   520  	allowedHeaders := []string{
   521  		"User-Agent", "Accept", "X-Requested-With", "Access-Control-Request-Headers", "Access-Control-Request-Method", "Accept-Ranges", "Content-Encoding",
   522  		AuthorizationHeader, AcceptEncodingHeader, ContentTypeHeader, ContentDispositionHeader, RangeHeader, OriginHeader,
   523  		SwarmTagHeader, SwarmPinHeader, SwarmEncryptHeader, SwarmIndexDocumentHeader, SwarmErrorDocumentHeader, SwarmCollectionHeader, SwarmPostageBatchIdHeader, SwarmPostageStampHeader, SwarmDeferredUploadHeader, SwarmRedundancyLevelHeader, SwarmRedundancyStrategyHeader, SwarmRedundancyFallbackModeHeader, SwarmChunkRetrievalTimeoutHeader, SwarmLookAheadBufferSizeHeader, SwarmFeedIndexHeader, SwarmFeedIndexNextHeader, GasPriceHeader, GasLimitHeader, ImmutableHeader,
   524  	}
   525  	allowedHeadersStr := strings.Join(allowedHeaders, ", ")
   526  
   527  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   528  		if o := r.Header.Get(OriginHeader); o != "" && s.checkOrigin(r) {
   529  			w.Header().Set("Access-Control-Allow-Credentials", "true")
   530  			w.Header().Set("Access-Control-Allow-Origin", o)
   531  			w.Header().Set("Access-Control-Allow-Headers", allowedHeadersStr)
   532  			w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS, POST, PUT, DELETE")
   533  			w.Header().Set("Access-Control-Max-Age", "3600")
   534  		}
   535  		h.ServeHTTP(w, r)
   536  	})
   537  }
   538  
   539  // checkOrigin returns true if the origin is not set or is equal to the request host.
   540  func (s *Service) checkOrigin(r *http.Request) bool {
   541  	origin := r.Header[OriginHeader]
   542  	if len(origin) == 0 {
   543  		return true
   544  	}
   545  	scheme := "http"
   546  	if r.TLS != nil {
   547  		scheme = "https"
   548  	}
   549  	hosts := append(s.CORSAllowedOrigins, scheme+"://"+r.Host)
   550  	for _, v := range hosts {
   551  		if equalASCIIFold(origin[0], v) || v == "*" {
   552  			return true
   553  		}
   554  	}
   555  
   556  	return false
   557  }
   558  
   559  // validationError is a custom error type for validation errors.
   560  type validationError struct {
   561  	Entry string
   562  	Value interface{}
   563  	Cause error
   564  }
   565  
   566  // Error implements the error interface.
   567  func (e *validationError) Error() string {
   568  	return fmt.Sprintf("`%s=%v`: %v", e.Entry, e.Value, e.Cause)
   569  }
   570  
   571  // mapStructure maps the input into output struct and validates the output.
   572  // It's a helper method for the handlers, which reduces the chattiness
   573  // of the code.
   574  func (s *Service) mapStructure(input, output interface{}) func(string, log.Logger, http.ResponseWriter) {
   575  	// response unifies the response format for parsing and validation errors.
   576  	response := func(err error) func(string, log.Logger, http.ResponseWriter) {
   577  		return func(msg string, logger log.Logger, w http.ResponseWriter) {
   578  			var merr *multierror.Error
   579  			if !errors.As(err, &merr) {
   580  				logger.Debug("mapping and validation failed", "error", err)
   581  				logger.Error(err, "mapping and validation failed")
   582  				jsonhttp.InternalServerError(w, err)
   583  				return
   584  			}
   585  
   586  			logger.Debug(msg, "error", err)
   587  			logger.Error(err, msg)
   588  
   589  			resp := jsonhttp.StatusResponse{
   590  				Message: msg,
   591  				Code:    http.StatusBadRequest,
   592  			}
   593  			for _, err := range merr.Errors {
   594  				var perr *parseError
   595  				if errors.As(err, &perr) {
   596  					resp.Reasons = append(resp.Reasons, jsonhttp.Reason{
   597  						Field: perr.Entry,
   598  						Error: perr.Cause.Error(),
   599  					})
   600  				}
   601  				var verr *validationError
   602  				if errors.As(err, &verr) {
   603  					resp.Reasons = append(resp.Reasons, jsonhttp.Reason{
   604  						Field: verr.Entry,
   605  						Error: verr.Cause.Error(),
   606  					})
   607  				}
   608  			}
   609  			jsonhttp.BadRequest(w, resp)
   610  		}
   611  	}
   612  
   613  	if err := mapStructure(input, output, s.preMapHooks); err != nil {
   614  		return response(err)
   615  	}
   616  
   617  	if err := s.validate.Struct(output); err != nil {
   618  		var errs validator.ValidationErrors
   619  		if !errors.As(err, &errs) {
   620  			return response(err)
   621  		}
   622  
   623  		vErrs := &multierror.Error{ErrorFormat: flattenErrorsFormat}
   624  		for _, err := range errs {
   625  			val := err.Value()
   626  			switch v := err.Value().(type) {
   627  			case []byte:
   628  				val = string(v)
   629  			}
   630  			vErrs = multierror.Append(vErrs,
   631  				&validationError{
   632  					Entry: strings.ToLower(err.Field()),
   633  					Value: val,
   634  					Cause: fmt.Errorf("want %s:%s", err.Tag(), err.Param()),
   635  				})
   636  		}
   637  		return response(vErrs.ErrorOrNil())
   638  	}
   639  
   640  	return nil
   641  }
   642  
   643  // equalASCIIFold returns true if s is equal to t with ASCII case folding as
   644  // defined in RFC 4790.
   645  func equalASCIIFold(s, t string) bool {
   646  	for s != "" && t != "" {
   647  		sr, size := utf8.DecodeRuneInString(s)
   648  		s = s[size:]
   649  		tr, size := utf8.DecodeRuneInString(t)
   650  		t = t[size:]
   651  		if sr == tr {
   652  			continue
   653  		}
   654  		if 'A' <= sr && sr <= 'Z' {
   655  			sr = sr + 'a' - 'A'
   656  		}
   657  		if 'A' <= tr && tr <= 'Z' {
   658  			tr = tr + 'a' - 'A'
   659  		}
   660  		if sr != tr {
   661  			return false
   662  		}
   663  	}
   664  	return s == t
   665  }
   666  
   667  type putterOptions struct {
   668  	BatchID  []byte
   669  	TagID    uint64
   670  	Deferred bool
   671  	Pin      bool
   672  }
   673  
   674  type putterSessionWrapper struct {
   675  	storer.PutterSession
   676  	stamper postage.Stamper
   677  	save    func() error
   678  }
   679  
   680  func (p *putterSessionWrapper) Put(ctx context.Context, chunk swarm.Chunk) error {
   681  	stamp, err := p.stamper.Stamp(chunk.Address())
   682  	if err != nil {
   683  		return err
   684  	}
   685  	return p.PutterSession.Put(ctx, chunk.WithStamp(stamp))
   686  }
   687  
   688  func (p *putterSessionWrapper) Done(ref swarm.Address) error {
   689  	return errors.Join(p.PutterSession.Done(ref), p.save())
   690  }
   691  
   692  func (p *putterSessionWrapper) Cleanup() error {
   693  	return errors.Join(p.PutterSession.Cleanup(), p.save())
   694  }
   695  
   696  func (s *Service) getStamper(batchID []byte) (postage.Stamper, func() error, error) {
   697  	exists, err := s.batchStore.Exists(batchID)
   698  	if err != nil {
   699  		return nil, nil, fmt.Errorf("batch exists: %w", err)
   700  	}
   701  
   702  	issuer, save, err := s.post.GetStampIssuer(batchID)
   703  	if err != nil {
   704  		return nil, nil, fmt.Errorf("stamp issuer: %w", err)
   705  	}
   706  
   707  	if usable := exists && s.post.IssuerUsable(issuer); !usable {
   708  		return nil, nil, errBatchUnusable
   709  	}
   710  
   711  	return postage.NewStamper(s.stamperStore, issuer, s.signer), save, nil
   712  }
   713  
   714  func (s *Service) newStamperPutter(ctx context.Context, opts putterOptions) (storer.PutterSession, error) {
   715  	if !opts.Deferred && s.beeMode == DevMode {
   716  		return nil, errUnsupportedDevNodeOperation
   717  	}
   718  
   719  	stamper, save, err := s.getStamper(opts.BatchID)
   720  	if err != nil {
   721  		return nil, fmt.Errorf("get stamper: %w", err)
   722  	}
   723  
   724  	var session storer.PutterSession
   725  	if opts.Deferred || opts.Pin {
   726  		session, err = s.storer.Upload(ctx, opts.Pin, opts.TagID)
   727  	} else {
   728  		session = s.storer.DirectUpload()
   729  	}
   730  
   731  	if err != nil {
   732  		return nil, fmt.Errorf("failed creating session: %w", err)
   733  	}
   734  
   735  	return &putterSessionWrapper{
   736  		PutterSession: session,
   737  		stamper:       stamper,
   738  		save:          save,
   739  	}, nil
   740  }
   741  
   742  func (s *Service) newStampedPutter(ctx context.Context, opts putterOptions, stamp *postage.Stamp) (storer.PutterSession, error) {
   743  	if !opts.Deferred && s.beeMode == DevMode {
   744  		return nil, errUnsupportedDevNodeOperation
   745  	}
   746  
   747  	storedBatch, err := s.batchStore.Get(stamp.BatchID())
   748  	if err != nil {
   749  		return nil, errInvalidPostageBatch
   750  	}
   751  
   752  	var session storer.PutterSession
   753  	if opts.Deferred || opts.Pin {
   754  		session, err = s.storer.Upload(ctx, opts.Pin, opts.TagID)
   755  		if err != nil {
   756  			return nil, fmt.Errorf("failed creating session: %w", err)
   757  		}
   758  	} else {
   759  		session = s.storer.DirectUpload()
   760  	}
   761  
   762  	stamper := postage.NewPresignedStamper(stamp, storedBatch.Owner)
   763  
   764  	return &putterSessionWrapper{
   765  		PutterSession: session,
   766  		stamper:       stamper,
   767  		save:          func() error { return nil },
   768  	}, nil
   769  }
   770  
   771  type pipelineFunc func(context.Context, io.Reader) (swarm.Address, error)
   772  
   773  func requestPipelineFn(s storage.Putter, encrypt bool, rLevel redundancy.Level) pipelineFunc {
   774  	return func(ctx context.Context, r io.Reader) (swarm.Address, error) {
   775  		pipe := builder.NewPipelineBuilder(ctx, s, encrypt, rLevel)
   776  		return builder.FeedPipeline(ctx, pipe, r)
   777  	}
   778  }
   779  
   780  func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface {
   781  	return func() pipeline.Interface {
   782  		return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel)
   783  	}
   784  }
   785  
   786  type cleanupOnErrWriter struct {
   787  	http.ResponseWriter
   788  	logger log.Logger
   789  	onErr  func() error
   790  }
   791  
   792  func (r *cleanupOnErrWriter) WriteHeader(statusCode int) {
   793  	// if there is an error status returned, cleanup.
   794  	if statusCode >= http.StatusBadRequest {
   795  		err := r.onErr()
   796  		if err != nil {
   797  			r.logger.Debug("failed cleaning up", "err", err)
   798  		}
   799  	}
   800  	r.ResponseWriter.WriteHeader(statusCode)
   801  }
   802  
   803  // CalculateNumberOfChunks calculates the number of chunks in an arbitrary
   804  // content length.
   805  func CalculateNumberOfChunks(contentLength int64, isEncrypted bool) int64 {
   806  	if contentLength <= swarm.ChunkSize {
   807  		return 1
   808  	}
   809  	branchingFactor := swarm.Branches
   810  	if isEncrypted {
   811  		branchingFactor = swarm.EncryptedBranches
   812  	}
   813  
   814  	dataChunks := math.Ceil(float64(contentLength) / float64(swarm.ChunkSize))
   815  	totalChunks := dataChunks
   816  	intermediate := dataChunks / float64(branchingFactor)
   817  
   818  	for intermediate > 1 {
   819  		totalChunks += math.Ceil(intermediate)
   820  		intermediate = intermediate / float64(branchingFactor)
   821  	}
   822  
   823  	return int64(totalChunks) + 1
   824  }
   825  
   826  // defaultUploadMethod returns true for deferred when the deferred header is not present.
   827  func defaultUploadMethod(deferred *bool) bool {
   828  	if deferred == nil {
   829  		return true
   830  	}
   831  
   832  	return *deferred
   833  }