github.com/divan/go-ethereum@v1.8.14-0.20180820134928-1de9ada4016d/swarm/api/api.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "archive/tar" 21 "context" 22 "crypto/ecdsa" 23 "encoding/hex" 24 "errors" 25 "fmt" 26 "io" 27 "math/big" 28 "net/http" 29 "path" 30 "strings" 31 32 "bytes" 33 "mime" 34 "path/filepath" 35 "time" 36 37 "github.com/ethereum/go-ethereum/common" 38 "github.com/ethereum/go-ethereum/contracts/ens" 39 "github.com/ethereum/go-ethereum/core/types" 40 "github.com/ethereum/go-ethereum/metrics" 41 "github.com/ethereum/go-ethereum/swarm/log" 42 "github.com/ethereum/go-ethereum/swarm/multihash" 43 "github.com/ethereum/go-ethereum/swarm/spancontext" 44 "github.com/ethereum/go-ethereum/swarm/storage" 45 "github.com/ethereum/go-ethereum/swarm/storage/mru" 46 opentracing "github.com/opentracing/opentracing-go" 47 ) 48 49 var ( 50 ErrNotFound = errors.New("not found") 51 ) 52 53 var ( 54 apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil) 55 apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil) 56 apiPutCount = metrics.NewRegisteredCounter("api.put.count", nil) 57 apiPutFail = metrics.NewRegisteredCounter("api.put.fail", nil) 58 apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil) 59 apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil) 60 apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil) 61 apiManifestUpdateCount = metrics.NewRegisteredCounter("api.manifestupdate.count", nil) 62 apiManifestUpdateFail = metrics.NewRegisteredCounter("api.manifestupdate.fail", nil) 63 apiManifestListCount = metrics.NewRegisteredCounter("api.manifestlist.count", nil) 64 apiManifestListFail = metrics.NewRegisteredCounter("api.manifestlist.fail", nil) 65 apiDeleteCount = metrics.NewRegisteredCounter("api.delete.count", nil) 66 apiDeleteFail = metrics.NewRegisteredCounter("api.delete.fail", nil) 67 apiGetTarCount = metrics.NewRegisteredCounter("api.gettar.count", nil) 68 apiGetTarFail = metrics.NewRegisteredCounter("api.gettar.fail", nil) 69 apiUploadTarCount = metrics.NewRegisteredCounter("api.uploadtar.count", nil) 70 apiUploadTarFail = metrics.NewRegisteredCounter("api.uploadtar.fail", nil) 71 apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil) 72 apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil) 73 apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil) 74 apiAddFileFail = metrics.NewRegisteredCounter("api.addfile.fail", nil) 75 apiRmFileCount = metrics.NewRegisteredCounter("api.removefile.count", nil) 76 apiRmFileFail = metrics.NewRegisteredCounter("api.removefile.fail", nil) 77 apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil) 78 apiAppendFileFail = metrics.NewRegisteredCounter("api.appendfile.fail", nil) 79 apiGetInvalid = metrics.NewRegisteredCounter("api.get.invalid", nil) 80 ) 81 82 // Resolver interface resolve a domain name to a hash using ENS 83 type Resolver interface { 84 Resolve(string) (common.Hash, error) 85 } 86 87 // ResolveValidator is used to validate the contained Resolver 88 type ResolveValidator interface { 89 Resolver 90 Owner(node [32]byte) (common.Address, error) 91 HeaderByNumber(context.Context, *big.Int) (*types.Header, error) 92 } 93 94 // NoResolverError is returned by MultiResolver.Resolve if no resolver 95 // can be found for the address. 96 type NoResolverError struct { 97 TLD string 98 } 99 100 // NewNoResolverError creates a NoResolverError for the given top level domain 101 func NewNoResolverError(tld string) *NoResolverError { 102 return &NoResolverError{TLD: tld} 103 } 104 105 // Error NoResolverError implements error 106 func (e *NoResolverError) Error() string { 107 if e.TLD == "" { 108 return "no ENS resolver" 109 } 110 return fmt.Sprintf("no ENS endpoint configured to resolve .%s TLD names", e.TLD) 111 } 112 113 // MultiResolver is used to resolve URL addresses based on their TLDs. 114 // Each TLD can have multiple resolvers, and the resoluton from the 115 // first one in the sequence will be returned. 116 type MultiResolver struct { 117 resolvers map[string][]ResolveValidator 118 nameHash func(string) common.Hash 119 } 120 121 // MultiResolverOption sets options for MultiResolver and is used as 122 // arguments for its constructor. 123 type MultiResolverOption func(*MultiResolver) 124 125 // MultiResolverOptionWithResolver adds a Resolver to a list of resolvers 126 // for a specific TLD. If TLD is an empty string, the resolver will be added 127 // to the list of default resolver, the ones that will be used for resolution 128 // of addresses which do not have their TLD resolver specified. 129 func MultiResolverOptionWithResolver(r ResolveValidator, tld string) MultiResolverOption { 130 return func(m *MultiResolver) { 131 m.resolvers[tld] = append(m.resolvers[tld], r) 132 } 133 } 134 135 // MultiResolverOptionWithNameHash is unused at the time of this writing 136 func MultiResolverOptionWithNameHash(nameHash func(string) common.Hash) MultiResolverOption { 137 return func(m *MultiResolver) { 138 m.nameHash = nameHash 139 } 140 } 141 142 // NewMultiResolver creates a new instance of MultiResolver. 143 func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) { 144 m = &MultiResolver{ 145 resolvers: make(map[string][]ResolveValidator), 146 nameHash: ens.EnsNode, 147 } 148 for _, o := range opts { 149 o(m) 150 } 151 return m 152 } 153 154 // Resolve resolves address by choosing a Resolver by TLD. 155 // If there are more default Resolvers, or for a specific TLD, 156 // the Hash from the the first one which does not return error 157 // will be returned. 158 func (m *MultiResolver) Resolve(addr string) (h common.Hash, err error) { 159 rs, err := m.getResolveValidator(addr) 160 if err != nil { 161 return h, err 162 } 163 for _, r := range rs { 164 h, err = r.Resolve(addr) 165 if err == nil { 166 return 167 } 168 } 169 return 170 } 171 172 // ValidateOwner checks the ENS to validate that the owner of the given domain is the given eth address 173 func (m *MultiResolver) ValidateOwner(name string, address common.Address) (bool, error) { 174 rs, err := m.getResolveValidator(name) 175 if err != nil { 176 return false, err 177 } 178 var addr common.Address 179 for _, r := range rs { 180 addr, err = r.Owner(m.nameHash(name)) 181 // we hide the error if it is not for the last resolver we check 182 if err == nil { 183 return addr == address, nil 184 } 185 } 186 return false, err 187 } 188 189 // HeaderByNumber uses the validator of the given domainname and retrieves the header for the given block number 190 func (m *MultiResolver) HeaderByNumber(ctx context.Context, name string, blockNr *big.Int) (*types.Header, error) { 191 rs, err := m.getResolveValidator(name) 192 if err != nil { 193 return nil, err 194 } 195 for _, r := range rs { 196 var header *types.Header 197 header, err = r.HeaderByNumber(ctx, blockNr) 198 // we hide the error if it is not for the last resolver we check 199 if err == nil { 200 return header, nil 201 } 202 } 203 return nil, err 204 } 205 206 // getResolveValidator uses the hostname to retrieve the resolver associated with the top level domain 207 func (m *MultiResolver) getResolveValidator(name string) ([]ResolveValidator, error) { 208 rs := m.resolvers[""] 209 tld := path.Ext(name) 210 if tld != "" { 211 tld = tld[1:] 212 rstld, ok := m.resolvers[tld] 213 if ok { 214 return rstld, nil 215 } 216 } 217 if len(rs) == 0 { 218 return rs, NewNoResolverError(tld) 219 } 220 return rs, nil 221 } 222 223 // SetNameHash sets the hasher function that hashes the domain into a name hash that ENS uses 224 func (m *MultiResolver) SetNameHash(nameHash func(string) common.Hash) { 225 m.nameHash = nameHash 226 } 227 228 /* 229 API implements webserver/file system related content storage and retrieval 230 on top of the FileStore 231 it is the public interface of the FileStore which is included in the ethereum stack 232 */ 233 type API struct { 234 resource *mru.Handler 235 fileStore *storage.FileStore 236 dns Resolver 237 Decryptor func(context.Context, string) DecryptFunc 238 } 239 240 // NewAPI the api constructor initialises a new API instance. 241 func NewAPI(fileStore *storage.FileStore, dns Resolver, resourceHandler *mru.Handler, pk *ecdsa.PrivateKey) (self *API) { 242 self = &API{ 243 fileStore: fileStore, 244 dns: dns, 245 resource: resourceHandler, 246 Decryptor: func(ctx context.Context, credentials string) DecryptFunc { 247 return self.doDecrypt(ctx, credentials, pk) 248 }, 249 } 250 return 251 } 252 253 // Upload to be used only in TEST 254 func (a *API) Upload(ctx context.Context, uploadDir, index string, toEncrypt bool) (hash string, err error) { 255 fs := NewFileSystem(a) 256 hash, err = fs.Upload(uploadDir, index, toEncrypt) 257 return hash, err 258 } 259 260 // Retrieve FileStore reader API 261 func (a *API) Retrieve(ctx context.Context, addr storage.Address) (reader storage.LazySectionReader, isEncrypted bool) { 262 return a.fileStore.Retrieve(ctx, addr) 263 } 264 265 // Store wraps the Store API call of the embedded FileStore 266 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) { 267 log.Debug("api.store", "size", size) 268 return a.fileStore.Store(ctx, data, size, toEncrypt) 269 } 270 271 // ErrResolve is returned when an URI cannot be resolved from ENS. 272 type ErrResolve error 273 274 // Resolve a name into a content-addressed hash 275 // where address could be an ENS name, or a content addressed hash 276 func (a *API) Resolve(ctx context.Context, address string) (storage.Address, error) { 277 // if DNS is not configured, return an error 278 if a.dns == nil { 279 if hashMatcher.MatchString(address) { 280 return common.Hex2Bytes(address), nil 281 } 282 apiResolveFail.Inc(1) 283 return nil, fmt.Errorf("no DNS to resolve name: %q", address) 284 } 285 // try and resolve the address 286 resolved, err := a.dns.Resolve(address) 287 if err != nil { 288 if hashMatcher.MatchString(address) { 289 return common.Hex2Bytes(address), nil 290 } 291 return nil, err 292 } 293 return resolved[:], nil 294 } 295 296 // Resolve resolves a URI to an Address using the MultiResolver. 297 func (a *API) ResolveURI(ctx context.Context, uri *URI, credentials string) (storage.Address, error) { 298 apiResolveCount.Inc(1) 299 log.Trace("resolving", "uri", uri.Addr) 300 301 var sp opentracing.Span 302 ctx, sp = spancontext.StartSpan( 303 ctx, 304 "api.resolve") 305 defer sp.Finish() 306 307 // if the URI is immutable, check if the address looks like a hash 308 if uri.Immutable() { 309 key := uri.Address() 310 if key == nil { 311 return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr) 312 } 313 return key, nil 314 } 315 316 addr, err := a.Resolve(ctx, uri.Addr) 317 if err != nil { 318 return nil, err 319 } 320 321 if uri.Path == "" { 322 return addr, nil 323 } 324 walker, err := a.NewManifestWalker(ctx, addr, a.Decryptor(ctx, credentials), nil) 325 if err != nil { 326 return nil, err 327 } 328 var entry *ManifestEntry 329 walker.Walk(func(e *ManifestEntry) error { 330 // if the entry matches the path, set entry and stop 331 // the walk 332 if e.Path == uri.Path { 333 entry = e 334 // return an error to cancel the walk 335 return errors.New("found") 336 } 337 // ignore non-manifest files 338 if e.ContentType != ManifestType { 339 return nil 340 } 341 // if the manifest's path is a prefix of the 342 // requested path, recurse into it by returning 343 // nil and continuing the walk 344 if strings.HasPrefix(uri.Path, e.Path) { 345 return nil 346 } 347 return ErrSkipManifest 348 }) 349 if entry == nil { 350 return nil, errors.New("not found") 351 } 352 addr = storage.Address(common.Hex2Bytes(entry.Hash)) 353 return addr, nil 354 } 355 356 // Put provides singleton manifest creation on top of FileStore store 357 func (a *API) Put(ctx context.Context, content string, contentType string, toEncrypt bool) (k storage.Address, wait func(context.Context) error, err error) { 358 apiPutCount.Inc(1) 359 r := strings.NewReader(content) 360 key, waitContent, err := a.fileStore.Store(ctx, r, int64(len(content)), toEncrypt) 361 if err != nil { 362 apiPutFail.Inc(1) 363 return nil, nil, err 364 } 365 manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType) 366 r = strings.NewReader(manifest) 367 key, waitManifest, err := a.fileStore.Store(ctx, r, int64(len(manifest)), toEncrypt) 368 if err != nil { 369 apiPutFail.Inc(1) 370 return nil, nil, err 371 } 372 return key, func(ctx context.Context) error { 373 err := waitContent(ctx) 374 if err != nil { 375 return err 376 } 377 return waitManifest(ctx) 378 }, nil 379 } 380 381 // Get uses iterative manifest retrieval and prefix matching 382 // to resolve basePath to content using FileStore retrieve 383 // it returns a section reader, mimeType, status, the key of the actual content and an error 384 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) { 385 log.Debug("api.get", "key", manifestAddr, "path", path) 386 apiGetCount.Inc(1) 387 trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt) 388 if err != nil { 389 apiGetNotFound.Inc(1) 390 status = http.StatusNotFound 391 return nil, "", http.StatusNotFound, nil, err 392 } 393 394 log.Debug("trie getting entry", "key", manifestAddr, "path", path) 395 entry, _ := trie.getEntry(path) 396 397 if entry != nil { 398 log.Debug("trie got entry", "key", manifestAddr, "path", path, "entry.Hash", entry.Hash) 399 400 if entry.ContentType == ManifestType { 401 log.Debug("entry is manifest", "key", manifestAddr, "new key", entry.Hash) 402 adr, err := hex.DecodeString(entry.Hash) 403 if err != nil { 404 return nil, "", 0, nil, err 405 } 406 return a.Get(ctx, decrypt, adr, entry.Path) 407 } 408 409 // we need to do some extra work if this is a mutable resource manifest 410 if entry.ContentType == ResourceContentType { 411 412 // get the resource rootAddr 413 log.Trace("resource type", "menifestAddr", manifestAddr, "hash", entry.Hash) 414 ctx, cancel := context.WithCancel(context.Background()) 415 defer cancel() 416 rootAddr := storage.Address(common.FromHex(entry.Hash)) 417 rsrc, err := a.resource.Load(ctx, rootAddr) 418 if err != nil { 419 apiGetNotFound.Inc(1) 420 status = http.StatusNotFound 421 log.Debug(fmt.Sprintf("get resource content error: %v", err)) 422 return reader, mimeType, status, nil, err 423 } 424 425 // use this key to retrieve the latest update 426 params := mru.LookupLatest(rootAddr) 427 rsrc, err = a.resource.Lookup(ctx, params) 428 if err != nil { 429 apiGetNotFound.Inc(1) 430 status = http.StatusNotFound 431 log.Debug(fmt.Sprintf("get resource content error: %v", err)) 432 return reader, mimeType, status, nil, err 433 } 434 435 // if it's multihash, we will transparently serve the content this multihash points to 436 // \TODO this resolve is rather expensive all in all, review to see if it can be achieved cheaper 437 if rsrc.Multihash() { 438 439 // get the data of the update 440 _, rsrcData, err := a.resource.GetContent(rootAddr) 441 if err != nil { 442 apiGetNotFound.Inc(1) 443 status = http.StatusNotFound 444 log.Warn(fmt.Sprintf("get resource content error: %v", err)) 445 return reader, mimeType, status, nil, err 446 } 447 448 // validate that data as multihash 449 decodedMultihash, err := multihash.FromMultihash(rsrcData) 450 if err != nil { 451 apiGetInvalid.Inc(1) 452 status = http.StatusUnprocessableEntity 453 log.Warn("invalid resource multihash", "err", err) 454 return reader, mimeType, status, nil, err 455 } 456 manifestAddr = storage.Address(decodedMultihash) 457 log.Trace("resource is multihash", "key", manifestAddr) 458 459 // get the manifest the multihash digest points to 460 trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt) 461 if err != nil { 462 apiGetNotFound.Inc(1) 463 status = http.StatusNotFound 464 log.Warn(fmt.Sprintf("loadManifestTrie (resource multihash) error: %v", err)) 465 return reader, mimeType, status, nil, err 466 } 467 468 // finally, get the manifest entry 469 // it will always be the entry on path "" 470 entry, _ = trie.getEntry(path) 471 if entry == nil { 472 status = http.StatusNotFound 473 apiGetNotFound.Inc(1) 474 err = fmt.Errorf("manifest (resource multihash) entry for '%s' not found", path) 475 log.Trace("manifest (resource multihash) entry not found", "key", manifestAddr, "path", path) 476 return reader, mimeType, status, nil, err 477 } 478 479 } else { 480 // data is returned verbatim since it's not a multihash 481 return rsrc, "application/octet-stream", http.StatusOK, nil, nil 482 } 483 } 484 485 // regardless of resource update manifests or normal manifests we will converge at this point 486 // get the key the manifest entry points to and serve it if it's unambiguous 487 contentAddr = common.Hex2Bytes(entry.Hash) 488 status = entry.Status 489 if status == http.StatusMultipleChoices { 490 apiGetHTTP300.Inc(1) 491 return nil, entry.ContentType, status, contentAddr, err 492 } 493 mimeType = entry.ContentType 494 log.Debug("content lookup key", "key", contentAddr, "mimetype", mimeType) 495 reader, _ = a.fileStore.Retrieve(ctx, contentAddr) 496 } else { 497 // no entry found 498 status = http.StatusNotFound 499 apiGetNotFound.Inc(1) 500 err = fmt.Errorf("manifest entry for '%s' not found", path) 501 log.Trace("manifest entry not found", "key", contentAddr, "path", path) 502 } 503 return 504 } 505 506 func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Address, error) { 507 apiDeleteCount.Inc(1) 508 uri, err := Parse("bzz:/" + addr) 509 if err != nil { 510 apiDeleteFail.Inc(1) 511 return nil, err 512 } 513 key, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) 514 515 if err != nil { 516 return nil, err 517 } 518 newKey, err := a.UpdateManifest(ctx, key, func(mw *ManifestWriter) error { 519 log.Debug(fmt.Sprintf("removing %s from manifest %s", path, key.Log())) 520 return mw.RemoveEntry(path) 521 }) 522 if err != nil { 523 apiDeleteFail.Inc(1) 524 return nil, err 525 } 526 527 return newKey, nil 528 } 529 530 // GetDirectoryTar fetches a requested directory as a tarstream 531 // it returns an io.Reader and an error. Do not forget to Close() the returned ReadCloser 532 func (a *API) GetDirectoryTar(ctx context.Context, decrypt DecryptFunc, uri *URI) (io.ReadCloser, error) { 533 apiGetTarCount.Inc(1) 534 addr, err := a.Resolve(ctx, uri.Addr) 535 if err != nil { 536 return nil, err 537 } 538 walker, err := a.NewManifestWalker(ctx, addr, decrypt, nil) 539 if err != nil { 540 apiGetTarFail.Inc(1) 541 return nil, err 542 } 543 544 piper, pipew := io.Pipe() 545 546 tw := tar.NewWriter(pipew) 547 548 go func() { 549 err := walker.Walk(func(entry *ManifestEntry) error { 550 // ignore manifests (walk will recurse into them) 551 if entry.ContentType == ManifestType { 552 return nil 553 } 554 555 // retrieve the entry's key and size 556 reader, _ := a.Retrieve(ctx, storage.Address(common.Hex2Bytes(entry.Hash))) 557 size, err := reader.Size(ctx, nil) 558 if err != nil { 559 return err 560 } 561 562 // write a tar header for the entry 563 hdr := &tar.Header{ 564 Name: entry.Path, 565 Mode: entry.Mode, 566 Size: size, 567 ModTime: entry.ModTime, 568 Xattrs: map[string]string{ 569 "user.swarm.content-type": entry.ContentType, 570 }, 571 } 572 573 if err := tw.WriteHeader(hdr); err != nil { 574 return err 575 } 576 577 // copy the file into the tar stream 578 n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) 579 if err != nil { 580 return err 581 } else if n != size { 582 return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) 583 } 584 585 return nil 586 }) 587 // close tar writer before closing pipew 588 // to flush remaining data to pipew 589 // regardless of error value 590 tw.Close() 591 if err != nil { 592 apiGetTarFail.Inc(1) 593 pipew.CloseWithError(err) 594 } else { 595 pipew.Close() 596 } 597 }() 598 599 return piper, nil 600 } 601 602 // GetManifestList lists the manifest entries for the specified address and prefix 603 // and returns it as a ManifestList 604 func (a *API) GetManifestList(ctx context.Context, decryptor DecryptFunc, addr storage.Address, prefix string) (list ManifestList, err error) { 605 apiManifestListCount.Inc(1) 606 walker, err := a.NewManifestWalker(ctx, addr, decryptor, nil) 607 if err != nil { 608 apiManifestListFail.Inc(1) 609 return ManifestList{}, err 610 } 611 612 err = walker.Walk(func(entry *ManifestEntry) error { 613 // handle non-manifest files 614 if entry.ContentType != ManifestType { 615 // ignore the file if it doesn't have the specified prefix 616 if !strings.HasPrefix(entry.Path, prefix) { 617 return nil 618 } 619 620 // if the path after the prefix contains a slash, add a 621 // common prefix to the list, otherwise add the entry 622 suffix := strings.TrimPrefix(entry.Path, prefix) 623 if index := strings.Index(suffix, "/"); index > -1 { 624 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 625 return nil 626 } 627 if entry.Path == "" { 628 entry.Path = "/" 629 } 630 list.Entries = append(list.Entries, entry) 631 return nil 632 } 633 634 // if the manifest's path is a prefix of the specified prefix 635 // then just recurse into the manifest by returning nil and 636 // continuing the walk 637 if strings.HasPrefix(prefix, entry.Path) { 638 return nil 639 } 640 641 // if the manifest's path has the specified prefix, then if the 642 // path after the prefix contains a slash, add a common prefix 643 // to the list and skip the manifest, otherwise recurse into 644 // the manifest by returning nil and continuing the walk 645 if strings.HasPrefix(entry.Path, prefix) { 646 suffix := strings.TrimPrefix(entry.Path, prefix) 647 if index := strings.Index(suffix, "/"); index > -1 { 648 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 649 return ErrSkipManifest 650 } 651 return nil 652 } 653 654 // the manifest neither has the prefix or needs recursing in to 655 // so just skip it 656 return ErrSkipManifest 657 }) 658 659 if err != nil { 660 apiManifestListFail.Inc(1) 661 return ManifestList{}, err 662 } 663 664 return list, nil 665 } 666 667 func (a *API) UpdateManifest(ctx context.Context, addr storage.Address, update func(mw *ManifestWriter) error) (storage.Address, error) { 668 apiManifestUpdateCount.Inc(1) 669 mw, err := a.NewManifestWriter(ctx, addr, nil) 670 if err != nil { 671 apiManifestUpdateFail.Inc(1) 672 return nil, err 673 } 674 675 if err := update(mw); err != nil { 676 apiManifestUpdateFail.Inc(1) 677 return nil, err 678 } 679 680 addr, err = mw.Store() 681 if err != nil { 682 apiManifestUpdateFail.Inc(1) 683 return nil, err 684 } 685 log.Debug(fmt.Sprintf("generated manifest %s", addr)) 686 return addr, nil 687 } 688 689 // Modify loads manifest and checks the content hash before recalculating and storing the manifest. 690 func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) { 691 apiModifyCount.Inc(1) 692 quitC := make(chan bool) 693 trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) 694 if err != nil { 695 apiModifyFail.Inc(1) 696 return nil, err 697 } 698 if contentHash != "" { 699 entry := newManifestTrieEntry(&ManifestEntry{ 700 Path: path, 701 ContentType: contentType, 702 }, nil) 703 entry.Hash = contentHash 704 trie.addEntry(entry, quitC) 705 } else { 706 trie.deleteEntry(path, quitC) 707 } 708 709 if err := trie.recalcAndStore(); err != nil { 710 apiModifyFail.Inc(1) 711 return nil, err 712 } 713 return trie.ref, nil 714 } 715 716 // AddFile creates a new manifest entry, adds it to swarm, then adds a file to swarm. 717 func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []byte, nameresolver bool) (storage.Address, string, error) { 718 apiAddFileCount.Inc(1) 719 720 uri, err := Parse("bzz:/" + mhash) 721 if err != nil { 722 apiAddFileFail.Inc(1) 723 return nil, "", err 724 } 725 mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) 726 if err != nil { 727 apiAddFileFail.Inc(1) 728 return nil, "", err 729 } 730 731 // trim the root dir we added 732 if path[:1] == "/" { 733 path = path[1:] 734 } 735 736 entry := &ManifestEntry{ 737 Path: filepath.Join(path, fname), 738 ContentType: mime.TypeByExtension(filepath.Ext(fname)), 739 Mode: 0700, 740 Size: int64(len(content)), 741 ModTime: time.Now(), 742 } 743 744 mw, err := a.NewManifestWriter(ctx, mkey, nil) 745 if err != nil { 746 apiAddFileFail.Inc(1) 747 return nil, "", err 748 } 749 750 fkey, err := mw.AddEntry(ctx, bytes.NewReader(content), entry) 751 if err != nil { 752 apiAddFileFail.Inc(1) 753 return nil, "", err 754 } 755 756 newMkey, err := mw.Store() 757 if err != nil { 758 apiAddFileFail.Inc(1) 759 return nil, "", err 760 761 } 762 763 return fkey, newMkey.String(), nil 764 } 765 766 func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestPath, defaultPath string, mw *ManifestWriter) (storage.Address, error) { 767 apiUploadTarCount.Inc(1) 768 var contentKey storage.Address 769 tr := tar.NewReader(bodyReader) 770 defer bodyReader.Close() 771 var defaultPathFound bool 772 for { 773 hdr, err := tr.Next() 774 if err == io.EOF { 775 break 776 } else if err != nil { 777 apiUploadTarFail.Inc(1) 778 return nil, fmt.Errorf("error reading tar stream: %s", err) 779 } 780 781 // only store regular files 782 if !hdr.FileInfo().Mode().IsRegular() { 783 continue 784 } 785 786 // add the entry under the path from the request 787 manifestPath := path.Join(manifestPath, hdr.Name) 788 entry := &ManifestEntry{ 789 Path: manifestPath, 790 ContentType: hdr.Xattrs["user.swarm.content-type"], 791 Mode: hdr.Mode, 792 Size: hdr.Size, 793 ModTime: hdr.ModTime, 794 } 795 contentKey, err = mw.AddEntry(ctx, tr, entry) 796 if err != nil { 797 apiUploadTarFail.Inc(1) 798 return nil, fmt.Errorf("error adding manifest entry from tar stream: %s", err) 799 } 800 if hdr.Name == defaultPath { 801 entry := &ManifestEntry{ 802 Hash: contentKey.Hex(), 803 Path: "", // default entry 804 ContentType: hdr.Xattrs["user.swarm.content-type"], 805 Mode: hdr.Mode, 806 Size: hdr.Size, 807 ModTime: hdr.ModTime, 808 } 809 contentKey, err = mw.AddEntry(ctx, nil, entry) 810 if err != nil { 811 apiUploadTarFail.Inc(1) 812 return nil, fmt.Errorf("error adding default manifest entry from tar stream: %s", err) 813 } 814 defaultPathFound = true 815 } 816 } 817 if defaultPath != "" && !defaultPathFound { 818 return contentKey, fmt.Errorf("default path %q not found", defaultPath) 819 } 820 return contentKey, nil 821 } 822 823 // RemoveFile removes a file entry in a manifest. 824 func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname string, nameresolver bool) (string, error) { 825 apiRmFileCount.Inc(1) 826 827 uri, err := Parse("bzz:/" + mhash) 828 if err != nil { 829 apiRmFileFail.Inc(1) 830 return "", err 831 } 832 mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) 833 if err != nil { 834 apiRmFileFail.Inc(1) 835 return "", err 836 } 837 838 // trim the root dir we added 839 if path[:1] == "/" { 840 path = path[1:] 841 } 842 843 mw, err := a.NewManifestWriter(ctx, mkey, nil) 844 if err != nil { 845 apiRmFileFail.Inc(1) 846 return "", err 847 } 848 849 err = mw.RemoveEntry(filepath.Join(path, fname)) 850 if err != nil { 851 apiRmFileFail.Inc(1) 852 return "", err 853 } 854 855 newMkey, err := mw.Store() 856 if err != nil { 857 apiRmFileFail.Inc(1) 858 return "", err 859 860 } 861 862 return newMkey.String(), nil 863 } 864 865 // AppendFile removes old manifest, appends file entry to new manifest and adds it to Swarm. 866 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) { 867 apiAppendFileCount.Inc(1) 868 869 buffSize := offset + addSize 870 if buffSize < existingSize { 871 buffSize = existingSize 872 } 873 874 buf := make([]byte, buffSize) 875 876 oldReader, _ := a.Retrieve(ctx, oldAddr) 877 io.ReadAtLeast(oldReader, buf, int(offset)) 878 879 newReader := bytes.NewReader(content) 880 io.ReadAtLeast(newReader, buf[offset:], int(addSize)) 881 882 if buffSize < existingSize { 883 io.ReadAtLeast(oldReader, buf[addSize:], int(buffSize)) 884 } 885 886 combinedReader := bytes.NewReader(buf) 887 totalSize := int64(len(buf)) 888 889 // TODO(jmozah): to append using pyramid chunker when it is ready 890 //oldReader := a.Retrieve(oldKey) 891 //newReader := bytes.NewReader(content) 892 //combinedReader := io.MultiReader(oldReader, newReader) 893 894 uri, err := Parse("bzz:/" + mhash) 895 if err != nil { 896 apiAppendFileFail.Inc(1) 897 return nil, "", err 898 } 899 mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) 900 if err != nil { 901 apiAppendFileFail.Inc(1) 902 return nil, "", err 903 } 904 905 // trim the root dir we added 906 if path[:1] == "/" { 907 path = path[1:] 908 } 909 910 mw, err := a.NewManifestWriter(ctx, mkey, nil) 911 if err != nil { 912 apiAppendFileFail.Inc(1) 913 return nil, "", err 914 } 915 916 err = mw.RemoveEntry(filepath.Join(path, fname)) 917 if err != nil { 918 apiAppendFileFail.Inc(1) 919 return nil, "", err 920 } 921 922 entry := &ManifestEntry{ 923 Path: filepath.Join(path, fname), 924 ContentType: mime.TypeByExtension(filepath.Ext(fname)), 925 Mode: 0700, 926 Size: totalSize, 927 ModTime: time.Now(), 928 } 929 930 fkey, err := mw.AddEntry(ctx, io.Reader(combinedReader), entry) 931 if err != nil { 932 apiAppendFileFail.Inc(1) 933 return nil, "", err 934 } 935 936 newMkey, err := mw.Store() 937 if err != nil { 938 apiAppendFileFail.Inc(1) 939 return nil, "", err 940 941 } 942 943 return fkey, newMkey.String(), nil 944 } 945 946 // BuildDirectoryTree used by swarmfs_unix 947 func (a *API) BuildDirectoryTree(ctx context.Context, mhash string, nameresolver bool) (addr storage.Address, manifestEntryMap map[string]*manifestTrieEntry, err error) { 948 949 uri, err := Parse("bzz:/" + mhash) 950 if err != nil { 951 return nil, nil, err 952 } 953 addr, err = a.Resolve(ctx, uri.Addr) 954 if err != nil { 955 return nil, nil, err 956 } 957 958 quitC := make(chan bool) 959 rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) 960 if err != nil { 961 return nil, nil, fmt.Errorf("can't load manifest %v: %v", addr.String(), err) 962 } 963 964 manifestEntryMap = map[string]*manifestTrieEntry{} 965 err = rootTrie.listWithPrefix(uri.Path, quitC, func(entry *manifestTrieEntry, suffix string) { 966 manifestEntryMap[suffix] = entry 967 }) 968 969 if err != nil { 970 return nil, nil, fmt.Errorf("list with prefix failed %v: %v", addr.String(), err) 971 } 972 return addr, manifestEntryMap, nil 973 } 974 975 // ResourceLookup finds mutable resource updates at specific periods and versions 976 func (a *API) ResourceLookup(ctx context.Context, params *mru.LookupParams) (string, []byte, error) { 977 var err error 978 rsrc, err := a.resource.Load(ctx, params.RootAddr()) 979 if err != nil { 980 return "", nil, err 981 } 982 _, err = a.resource.Lookup(ctx, params) 983 if err != nil { 984 return "", nil, err 985 } 986 var data []byte 987 _, data, err = a.resource.GetContent(params.RootAddr()) 988 if err != nil { 989 return "", nil, err 990 } 991 return rsrc.Name(), data, nil 992 } 993 994 // Create Mutable resource 995 func (a *API) ResourceCreate(ctx context.Context, request *mru.Request) error { 996 return a.resource.New(ctx, request) 997 } 998 999 // ResourceNewRequest creates a Request object to update a specific mutable resource 1000 func (a *API) ResourceNewRequest(ctx context.Context, rootAddr storage.Address) (*mru.Request, error) { 1001 return a.resource.NewUpdateRequest(ctx, rootAddr) 1002 } 1003 1004 // ResourceUpdate updates a Mutable Resource with arbitrary data. 1005 // Upon retrieval the update will be retrieved verbatim as bytes. 1006 func (a *API) ResourceUpdate(ctx context.Context, request *mru.SignedResourceUpdate) (storage.Address, error) { 1007 return a.resource.Update(ctx, request) 1008 } 1009 1010 // ResourceHashSize returned the size of the digest produced by the Mutable Resource hashing function 1011 func (a *API) ResourceHashSize() int { 1012 return a.resource.HashSize 1013 } 1014 1015 // ResolveResourceManifest retrieves the Mutable Resource manifest for the given address, and returns the address of the metadata chunk. 1016 func (a *API) ResolveResourceManifest(ctx context.Context, addr storage.Address) (storage.Address, error) { 1017 trie, err := loadManifest(ctx, a.fileStore, addr, nil, NOOPDecrypt) 1018 if err != nil { 1019 return nil, fmt.Errorf("cannot load resource manifest: %v", err) 1020 } 1021 1022 entry, _ := trie.getEntry("") 1023 if entry.ContentType != ResourceContentType { 1024 return nil, fmt.Errorf("not a resource manifest: %s", addr) 1025 } 1026 1027 return storage.Address(common.FromHex(entry.Hash)), nil 1028 }