github.com/anthdm/go-ethereum@v1.8.4-0.20180412101906-60516c83b011/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 "fmt" 21 "io" 22 "net/http" 23 "path" 24 "regexp" 25 "strings" 26 "sync" 27 28 "bytes" 29 "mime" 30 "path/filepath" 31 "time" 32 33 "github.com/ethereum/go-ethereum/common" 34 "github.com/ethereum/go-ethereum/log" 35 "github.com/ethereum/go-ethereum/metrics" 36 "github.com/ethereum/go-ethereum/swarm/storage" 37 ) 38 39 var hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}") 40 41 //setup metrics 42 var ( 43 apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil) 44 apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil) 45 apiPutCount = metrics.NewRegisteredCounter("api.put.count", nil) 46 apiPutFail = metrics.NewRegisteredCounter("api.put.fail", nil) 47 apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil) 48 apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil) 49 apiGetHttp300 = metrics.NewRegisteredCounter("api.get.http.300", nil) 50 apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil) 51 apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil) 52 apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil) 53 apiAddFileFail = metrics.NewRegisteredCounter("api.addfile.fail", nil) 54 apiRmFileCount = metrics.NewRegisteredCounter("api.removefile.count", nil) 55 apiRmFileFail = metrics.NewRegisteredCounter("api.removefile.fail", nil) 56 apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil) 57 apiAppendFileFail = metrics.NewRegisteredCounter("api.appendfile.fail", nil) 58 ) 59 60 type Resolver interface { 61 Resolve(string) (common.Hash, error) 62 } 63 64 // NoResolverError is returned by MultiResolver.Resolve if no resolver 65 // can be found for the address. 66 type NoResolverError struct { 67 TLD string 68 } 69 70 func NewNoResolverError(tld string) *NoResolverError { 71 return &NoResolverError{TLD: tld} 72 } 73 74 func (e *NoResolverError) Error() string { 75 if e.TLD == "" { 76 return "no ENS resolver" 77 } 78 return fmt.Sprintf("no ENS endpoint configured to resolve .%s TLD names", e.TLD) 79 } 80 81 // MultiResolver is used to resolve URL addresses based on their TLDs. 82 // Each TLD can have multiple resolvers, and the resoluton from the 83 // first one in the sequence will be returned. 84 type MultiResolver struct { 85 resolvers map[string][]Resolver 86 } 87 88 // MultiResolverOption sets options for MultiResolver and is used as 89 // arguments for its constructor. 90 type MultiResolverOption func(*MultiResolver) 91 92 // MultiResolverOptionWithResolver adds a Resolver to a list of resolvers 93 // for a specific TLD. If TLD is an empty string, the resolver will be added 94 // to the list of default resolver, the ones that will be used for resolution 95 // of addresses which do not have their TLD resolver specified. 96 func MultiResolverOptionWithResolver(r Resolver, tld string) MultiResolverOption { 97 return func(m *MultiResolver) { 98 m.resolvers[tld] = append(m.resolvers[tld], r) 99 } 100 } 101 102 // NewMultiResolver creates a new instance of MultiResolver. 103 func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) { 104 m = &MultiResolver{ 105 resolvers: make(map[string][]Resolver), 106 } 107 for _, o := range opts { 108 o(m) 109 } 110 return m 111 } 112 113 // Resolve resolves address by choosing a Resolver by TLD. 114 // If there are more default Resolvers, or for a specific TLD, 115 // the Hash from the the first one which does not return error 116 // will be returned. 117 func (m MultiResolver) Resolve(addr string) (h common.Hash, err error) { 118 rs := m.resolvers[""] 119 tld := path.Ext(addr) 120 if tld != "" { 121 tld = tld[1:] 122 rstld, ok := m.resolvers[tld] 123 if ok { 124 rs = rstld 125 } 126 } 127 if rs == nil { 128 return h, NewNoResolverError(tld) 129 } 130 for _, r := range rs { 131 h, err = r.Resolve(addr) 132 if err == nil { 133 return 134 } 135 } 136 return 137 } 138 139 /* 140 Api implements webserver/file system related content storage and retrieval 141 on top of the dpa 142 it is the public interface of the dpa which is included in the ethereum stack 143 */ 144 type Api struct { 145 dpa *storage.DPA 146 dns Resolver 147 } 148 149 //the api constructor initialises 150 func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) { 151 self = &Api{ 152 dpa: dpa, 153 dns: dns, 154 } 155 return 156 } 157 158 // to be used only in TEST 159 func (self *Api) Upload(uploadDir, index string) (hash string, err error) { 160 fs := NewFileSystem(self) 161 hash, err = fs.Upload(uploadDir, index) 162 return hash, err 163 } 164 165 // DPA reader API 166 func (self *Api) Retrieve(key storage.Key) storage.LazySectionReader { 167 return self.dpa.Retrieve(key) 168 } 169 170 func (self *Api) Store(data io.Reader, size int64, wg *sync.WaitGroup) (key storage.Key, err error) { 171 return self.dpa.Store(data, size, wg, nil) 172 } 173 174 type ErrResolve error 175 176 // DNS Resolver 177 func (self *Api) Resolve(uri *URI) (storage.Key, error) { 178 apiResolveCount.Inc(1) 179 log.Trace(fmt.Sprintf("Resolving : %v", uri.Addr)) 180 181 // if the URI is immutable, check if the address is a hash 182 isHash := hashMatcher.MatchString(uri.Addr) 183 if uri.Immutable() || uri.DeprecatedImmutable() { 184 if !isHash { 185 return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr) 186 } 187 return common.Hex2Bytes(uri.Addr), nil 188 } 189 190 // if DNS is not configured, check if the address is a hash 191 if self.dns == nil { 192 if !isHash { 193 apiResolveFail.Inc(1) 194 return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr) 195 } 196 return common.Hex2Bytes(uri.Addr), nil 197 } 198 199 // try and resolve the address 200 resolved, err := self.dns.Resolve(uri.Addr) 201 if err == nil { 202 return resolved[:], nil 203 } else if !isHash { 204 apiResolveFail.Inc(1) 205 return nil, err 206 } 207 return common.Hex2Bytes(uri.Addr), nil 208 } 209 210 // Put provides singleton manifest creation on top of dpa store 211 func (self *Api) Put(content, contentType string) (storage.Key, error) { 212 apiPutCount.Inc(1) 213 r := strings.NewReader(content) 214 wg := &sync.WaitGroup{} 215 key, err := self.dpa.Store(r, int64(len(content)), wg, nil) 216 if err != nil { 217 apiPutFail.Inc(1) 218 return nil, err 219 } 220 manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType) 221 r = strings.NewReader(manifest) 222 key, err = self.dpa.Store(r, int64(len(manifest)), wg, nil) 223 if err != nil { 224 apiPutFail.Inc(1) 225 return nil, err 226 } 227 wg.Wait() 228 return key, nil 229 } 230 231 // Get uses iterative manifest retrieval and prefix matching 232 // to resolve basePath to content using dpa retrieve 233 // it returns a section reader, mimeType, status and an error 234 func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) { 235 apiGetCount.Inc(1) 236 trie, err := loadManifest(self.dpa, key, nil) 237 if err != nil { 238 apiGetNotFound.Inc(1) 239 status = http.StatusNotFound 240 log.Warn(fmt.Sprintf("loadManifestTrie error: %v", err)) 241 return 242 } 243 244 log.Trace(fmt.Sprintf("getEntry(%s)", path)) 245 246 entry, _ := trie.getEntry(path) 247 248 if entry != nil { 249 key = common.Hex2Bytes(entry.Hash) 250 status = entry.Status 251 if status == http.StatusMultipleChoices { 252 apiGetHttp300.Inc(1) 253 return 254 } else { 255 mimeType = entry.ContentType 256 log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType)) 257 reader = self.dpa.Retrieve(key) 258 } 259 } else { 260 status = http.StatusNotFound 261 apiGetNotFound.Inc(1) 262 err = fmt.Errorf("manifest entry for '%s' not found", path) 263 log.Warn(fmt.Sprintf("%v", err)) 264 } 265 return 266 } 267 268 func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) (storage.Key, error) { 269 apiModifyCount.Inc(1) 270 quitC := make(chan bool) 271 trie, err := loadManifest(self.dpa, key, quitC) 272 if err != nil { 273 apiModifyFail.Inc(1) 274 return nil, err 275 } 276 if contentHash != "" { 277 entry := newManifestTrieEntry(&ManifestEntry{ 278 Path: path, 279 ContentType: contentType, 280 }, nil) 281 entry.Hash = contentHash 282 trie.addEntry(entry, quitC) 283 } else { 284 trie.deleteEntry(path, quitC) 285 } 286 287 if err := trie.recalcAndStore(); err != nil { 288 apiModifyFail.Inc(1) 289 return nil, err 290 } 291 return trie.hash, nil 292 } 293 294 func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver bool) (storage.Key, string, error) { 295 apiAddFileCount.Inc(1) 296 297 uri, err := Parse("bzz:/" + mhash) 298 if err != nil { 299 apiAddFileFail.Inc(1) 300 return nil, "", err 301 } 302 mkey, err := self.Resolve(uri) 303 if err != nil { 304 apiAddFileFail.Inc(1) 305 return nil, "", err 306 } 307 308 // trim the root dir we added 309 if path[:1] == "/" { 310 path = path[1:] 311 } 312 313 entry := &ManifestEntry{ 314 Path: filepath.Join(path, fname), 315 ContentType: mime.TypeByExtension(filepath.Ext(fname)), 316 Mode: 0700, 317 Size: int64(len(content)), 318 ModTime: time.Now(), 319 } 320 321 mw, err := self.NewManifestWriter(mkey, nil) 322 if err != nil { 323 apiAddFileFail.Inc(1) 324 return nil, "", err 325 } 326 327 fkey, err := mw.AddEntry(bytes.NewReader(content), entry) 328 if err != nil { 329 apiAddFileFail.Inc(1) 330 return nil, "", err 331 } 332 333 newMkey, err := mw.Store() 334 if err != nil { 335 apiAddFileFail.Inc(1) 336 return nil, "", err 337 338 } 339 340 return fkey, newMkey.String(), nil 341 342 } 343 344 func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (string, error) { 345 apiRmFileCount.Inc(1) 346 347 uri, err := Parse("bzz:/" + mhash) 348 if err != nil { 349 apiRmFileFail.Inc(1) 350 return "", err 351 } 352 mkey, err := self.Resolve(uri) 353 if err != nil { 354 apiRmFileFail.Inc(1) 355 return "", err 356 } 357 358 // trim the root dir we added 359 if path[:1] == "/" { 360 path = path[1:] 361 } 362 363 mw, err := self.NewManifestWriter(mkey, nil) 364 if err != nil { 365 apiRmFileFail.Inc(1) 366 return "", err 367 } 368 369 err = mw.RemoveEntry(filepath.Join(path, fname)) 370 if err != nil { 371 apiRmFileFail.Inc(1) 372 return "", err 373 } 374 375 newMkey, err := mw.Store() 376 if err != nil { 377 apiRmFileFail.Inc(1) 378 return "", err 379 380 } 381 382 return newMkey.String(), nil 383 } 384 385 func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, content []byte, oldKey storage.Key, offset int64, addSize int64, nameresolver bool) (storage.Key, string, error) { 386 apiAppendFileCount.Inc(1) 387 388 buffSize := offset + addSize 389 if buffSize < existingSize { 390 buffSize = existingSize 391 } 392 393 buf := make([]byte, buffSize) 394 395 oldReader := self.Retrieve(oldKey) 396 io.ReadAtLeast(oldReader, buf, int(offset)) 397 398 newReader := bytes.NewReader(content) 399 io.ReadAtLeast(newReader, buf[offset:], int(addSize)) 400 401 if buffSize < existingSize { 402 io.ReadAtLeast(oldReader, buf[addSize:], int(buffSize)) 403 } 404 405 combinedReader := bytes.NewReader(buf) 406 totalSize := int64(len(buf)) 407 408 // TODO(jmozah): to append using pyramid chunker when it is ready 409 //oldReader := self.Retrieve(oldKey) 410 //newReader := bytes.NewReader(content) 411 //combinedReader := io.MultiReader(oldReader, newReader) 412 413 uri, err := Parse("bzz:/" + mhash) 414 if err != nil { 415 apiAppendFileFail.Inc(1) 416 return nil, "", err 417 } 418 mkey, err := self.Resolve(uri) 419 if err != nil { 420 apiAppendFileFail.Inc(1) 421 return nil, "", err 422 } 423 424 // trim the root dir we added 425 if path[:1] == "/" { 426 path = path[1:] 427 } 428 429 mw, err := self.NewManifestWriter(mkey, nil) 430 if err != nil { 431 apiAppendFileFail.Inc(1) 432 return nil, "", err 433 } 434 435 err = mw.RemoveEntry(filepath.Join(path, fname)) 436 if err != nil { 437 apiAppendFileFail.Inc(1) 438 return nil, "", err 439 } 440 441 entry := &ManifestEntry{ 442 Path: filepath.Join(path, fname), 443 ContentType: mime.TypeByExtension(filepath.Ext(fname)), 444 Mode: 0700, 445 Size: totalSize, 446 ModTime: time.Now(), 447 } 448 449 fkey, err := mw.AddEntry(io.Reader(combinedReader), entry) 450 if err != nil { 451 apiAppendFileFail.Inc(1) 452 return nil, "", err 453 } 454 455 newMkey, err := mw.Store() 456 if err != nil { 457 apiAppendFileFail.Inc(1) 458 return nil, "", err 459 460 } 461 462 return fkey, newMkey.String(), nil 463 464 } 465 466 func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storage.Key, manifestEntryMap map[string]*manifestTrieEntry, err error) { 467 468 uri, err := Parse("bzz:/" + mhash) 469 if err != nil { 470 return nil, nil, err 471 } 472 key, err = self.Resolve(uri) 473 if err != nil { 474 return nil, nil, err 475 } 476 477 quitC := make(chan bool) 478 rootTrie, err := loadManifest(self.dpa, key, quitC) 479 if err != nil { 480 return nil, nil, fmt.Errorf("can't load manifest %v: %v", key.String(), err) 481 } 482 483 manifestEntryMap = map[string]*manifestTrieEntry{} 484 err = rootTrie.listWithPrefix(uri.Path, quitC, func(entry *manifestTrieEntry, suffix string) { 485 manifestEntryMap[suffix] = entry 486 }) 487 488 if err != nil { 489 return nil, nil, fmt.Errorf("list with prefix failed %v: %v", key.String(), err) 490 } 491 return key, manifestEntryMap, nil 492 }