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