github.com/status-im/status-go@v1.1.0/server/handlers.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "database/sql" 6 "errors" 7 "image" 8 "image/color" 9 "math/big" 10 "net/http" 11 "net/url" 12 "os" 13 "path/filepath" 14 "strconv" 15 "time" 16 17 "github.com/golang/protobuf/proto" 18 19 "github.com/status-im/status-go/eth-node/types" 20 "github.com/status-im/status-go/protocol/protobuf" 21 22 "go.uber.org/zap" 23 24 eth_common "github.com/ethereum/go-ethereum/common" 25 26 "github.com/status-im/status-go/images" 27 "github.com/status-im/status-go/ipfs" 28 "github.com/status-im/status-go/multiaccounts" 29 "github.com/status-im/status-go/protocol/identity/colorhash" 30 "github.com/status-im/status-go/protocol/identity/ring" 31 "github.com/status-im/status-go/services/wallet/bigint" 32 ) 33 34 const ( 35 basePath = "/messages" 36 imagesPath = basePath + "/images" 37 audioPath = basePath + "/audio" 38 ipfsPath = "/ipfs" 39 discordAuthorsPath = "/discord/authors" 40 discordAttachmentsPath = basePath + "/discord/attachments" 41 LinkPreviewThumbnailPath = "/link-preview/thumbnail" 42 LinkPreviewFaviconPath = "/link-preview/favicon" 43 StatusLinkPreviewThumbnailPath = "/status-link-preview/thumbnail" 44 communityTokenImagesPath = "/communityTokenImages" 45 communityDescriptionImagesPath = "/communityDescriptionImages" 46 communityDescriptionTokenImagesPath = "/communityDescriptionTokenImages" 47 48 walletBasePath = "/wallet" 49 walletCommunityImagesPath = walletBasePath + "/communityImages" 50 walletCollectionImagesPath = walletBasePath + "/collectionImages" 51 walletCollectibleImagesPath = walletBasePath + "/collectibleImages" 52 53 // Handler routes for pairing 54 accountImagesPath = "/accountImages" 55 accountInitialsPath = "/accountInitials" 56 contactImagesPath = "/contactImages" 57 generateQRCode = "/GenerateQRCode" 58 ) 59 60 type HandlerPatternMap map[string]http.HandlerFunc 61 62 func handleRequestDBMissing(logger *zap.Logger) http.HandlerFunc { 63 return func(w http.ResponseWriter, r *http.Request) { 64 logger.Error("can't handle media request without appdb") 65 } 66 } 67 68 func handleRequestDownloaderMissing(logger *zap.Logger) http.HandlerFunc { 69 return func(w http.ResponseWriter, r *http.Request) { 70 logger.Error("can't handle media request without ipfs downloader") 71 } 72 } 73 74 type ImageParams struct { 75 KeyUID string 76 PublicKey string 77 ImageName string 78 ImagePath string 79 FullName string 80 InitialsLength int 81 FontFile string 82 FontSize float64 83 Color color.Color 84 BgSize int 85 BgColor color.Color 86 UppercaseRatio float64 87 Theme ring.Theme 88 Ring bool 89 RingWidth float64 90 IndicatorSize float64 91 IndicatorBorder float64 92 IndicatorCenterToEdge float64 93 IndicatorColor color.Color 94 95 AuthorID string 96 URL string 97 MessageID string 98 AttachmentID string 99 ImageID string 100 101 Hash string 102 Download bool 103 } 104 105 func ParseImageParams(logger *zap.Logger, params url.Values) ImageParams { 106 parsed := ImageParams{} 107 parsed.Color = color.Transparent 108 parsed.BgColor = color.Transparent 109 parsed.IndicatorColor = color.Transparent 110 parsed.UppercaseRatio = 1.0 111 112 keyUids := params["keyUid"] 113 if len(keyUids) != 0 { 114 parsed.KeyUID = keyUids[0] 115 } 116 117 pks := params["publicKey"] 118 if len(pks) != 0 { 119 parsed.PublicKey = pks[0] 120 } 121 122 imageNames := params["imageName"] 123 if len(imageNames) != 0 { 124 if filepath.IsAbs(imageNames[0]) { 125 if _, err := os.Stat(imageNames[0]); err == nil { 126 parsed.ImagePath = imageNames[0] 127 } else if errors.Is(err, os.ErrNotExist) { 128 logger.Error("ParseParams: image not exit", zap.String("imageName", imageNames[0])) 129 return parsed 130 } else { 131 logger.Error("ParseParams: failed to read image", zap.String("imageName", imageNames[0]), zap.Error(err)) 132 return parsed 133 } 134 } else { 135 parsed.ImageName = imageNames[0] 136 } 137 } 138 139 names := params["name"] 140 if len(names) != 0 { 141 parsed.FullName = names[0] 142 } 143 144 parsed.InitialsLength = 2 145 amountInitialsStr := params["length"] 146 if len(amountInitialsStr) != 0 { 147 amountInitials, err := strconv.Atoi(amountInitialsStr[0]) 148 if err != nil { 149 logger.Error("ParseParams: invalid initials length") 150 return parsed 151 } 152 parsed.InitialsLength = amountInitials 153 } 154 155 fontFiles := params["fontFile"] 156 if len(fontFiles) != 0 { 157 if _, err := os.Stat(fontFiles[0]); err == nil { 158 parsed.FontFile = fontFiles[0] 159 } else if errors.Is(err, os.ErrNotExist) { 160 logger.Error("ParseParams: font file not exit", zap.String("FontFile", fontFiles[0])) 161 return parsed 162 } else { 163 logger.Error("ParseParams: font file not exit", zap.String("FontFile", fontFiles[0]), zap.Error(err)) 164 return parsed 165 } 166 } 167 168 fontSizeStr := params["fontSize"] 169 if len(fontSizeStr) != 0 { 170 fontSize, err := strconv.ParseFloat(fontSizeStr[0], 64) 171 if err != nil { 172 logger.Error("ParseParams: invalid fontSize", zap.String("FontSize", fontSizeStr[0])) 173 return parsed 174 } 175 parsed.FontSize = fontSize 176 } 177 178 colors := params["color"] 179 if len(colors) != 0 { 180 textColor, err := images.ParseColor(colors[0]) 181 if err != nil { 182 logger.Error("ParseParams: invalid color", zap.String("Color", colors[0])) 183 return parsed 184 } 185 parsed.Color = textColor 186 } 187 188 sizeStrs := params["size"] 189 if len(sizeStrs) != 0 { 190 size, err := strconv.Atoi(sizeStrs[0]) 191 if err != nil { 192 logger.Error("ParseParams: invalid size", zap.String("size", sizeStrs[0])) 193 return parsed 194 } 195 parsed.BgSize = size 196 } 197 198 bgColors := params["bgColor"] 199 if len(bgColors) != 0 { 200 bgColor, err := images.ParseColor(bgColors[0]) 201 if err != nil { 202 logger.Error("ParseParams: invalid bgColor", zap.String("BgColor", bgColors[0])) 203 return parsed 204 } 205 parsed.BgColor = bgColor 206 } 207 208 uppercaseRatioStr := params["uppercaseRatio"] 209 if len(uppercaseRatioStr) != 0 { 210 uppercaseRatio, err := strconv.ParseFloat(uppercaseRatioStr[0], 64) 211 if err != nil { 212 logger.Error("ParseParams: invalid uppercaseRatio", zap.String("uppercaseRatio", uppercaseRatioStr[0])) 213 return parsed 214 } 215 parsed.UppercaseRatio = uppercaseRatio 216 } 217 218 indicatorColors := params["indicatorColor"] 219 if len(indicatorColors) != 0 { 220 indicatorColor, err := images.ParseColor(indicatorColors[0]) 221 if err != nil { 222 logger.Error("ParseParams: invalid indicatorColor", zap.String("IndicatorColor", indicatorColors[0])) 223 return parsed 224 } 225 parsed.IndicatorColor = indicatorColor 226 } 227 228 indicatorSizeStrs := params["indicatorSize"] 229 if len(indicatorSizeStrs) != 0 { 230 indicatorSize, err := strconv.ParseFloat(indicatorSizeStrs[0], 64) 231 if err != nil { 232 logger.Error("ParseParams: invalid indicatorSize", zap.String("indicatorSize", indicatorSizeStrs[0])) 233 indicatorSize = 0 234 } 235 parsed.IndicatorSize = indicatorSize 236 } 237 238 indicatorBorderStrs := params["indicatorBorder"] 239 if len(indicatorBorderStrs) != 0 { 240 indicatorBorder, err := strconv.ParseFloat(indicatorBorderStrs[0], 64) 241 if err != nil { 242 logger.Error("ParseParams: invalid indicatorBorder", zap.String("indicatorBorder", indicatorBorderStrs[0])) 243 indicatorBorder = 0 244 } 245 parsed.IndicatorBorder = indicatorBorder 246 } 247 248 indicatorCenterToEdgeStrs := params["indicatorCenterToEdge"] 249 if len(indicatorCenterToEdgeStrs) != 0 { 250 indicatorCenterToEdge, err := strconv.ParseFloat(indicatorCenterToEdgeStrs[0], 64) 251 if err != nil { 252 logger.Error("ParseParams: invalid indicatorCenterToEdge", zap.String("indicatorCenterToEdge", indicatorCenterToEdgeStrs[0])) 253 indicatorCenterToEdge = 0 254 } 255 parsed.IndicatorCenterToEdge = indicatorCenterToEdge 256 } 257 258 ringWidthStrs := params["ringWidth"] 259 if len(ringWidthStrs) != 0 { 260 ringWidth, err := strconv.ParseFloat(ringWidthStrs[0], 64) 261 if err != nil { 262 logger.Error("ParseParams: invalid indicatorSize", zap.String("ringWidth", ringWidthStrs[0])) 263 ringWidth = 0 264 } 265 parsed.RingWidth = ringWidth 266 } 267 268 parsed.Theme = getTheme(params, logger) 269 parsed.Ring = ringEnabled(params) 270 271 messageIDs := params["message-id"] 272 if len(messageIDs) != 0 { 273 parsed.MessageID = messageIDs[0] 274 } 275 276 messageIDs = params["messageId"] 277 if len(messageIDs) != 0 { 278 parsed.MessageID = messageIDs[0] 279 } 280 281 authorIds := params["authorId"] 282 if len(authorIds) != 0 { 283 parsed.AuthorID = authorIds[0] 284 } 285 286 if attachmentIDs := params["attachmentId"]; len(attachmentIDs) != 0 { 287 parsed.AttachmentID = attachmentIDs[0] 288 } 289 290 if imageIds := params["image-id"]; len(imageIds) != 0 { 291 parsed.ImageID = imageIds[0] 292 } 293 294 urls := params["url"] 295 if len(urls) != 0 { 296 parsed.URL = urls[0] 297 } 298 299 hash := params["hash"] 300 if len(hash) != 0 { 301 parsed.Hash = hash[0] 302 } 303 304 _, download := params["download"] 305 parsed.Download = download 306 307 return parsed 308 } 309 310 func handleAccountImagesImpl(multiaccountsDB *multiaccounts.Database, logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { 311 if parsed.KeyUID == "" { 312 logger.Error("handleAccountImagesImpl: no keyUid") 313 return 314 } 315 316 if parsed.ImageName == "" { 317 logger.Error("handleAccountImagesImpl: no imageName") 318 return 319 } 320 321 identityImage, err := multiaccountsDB.GetIdentityImage(parsed.KeyUID, parsed.ImageName) 322 if err != nil { 323 logger.Error("handleAccountImagesImpl: failed to load image.", zap.String("keyUid", parsed.KeyUID), zap.String("imageName", parsed.ImageName), zap.Error(err)) 324 return 325 } 326 327 if parsed.Ring && parsed.RingWidth == 0 { 328 logger.Error("handleAccountImagesImpl: no ringWidth.") 329 return 330 } 331 332 if parsed.BgSize == 0 { 333 parsed.BgSize = identityImage.Width 334 } 335 336 payload, err := images.RoundCrop(identityImage.Payload) 337 if err != nil { 338 logger.Error("handleAccountImagesImpl: failed to crop image.", zap.String("keyUid", parsed.KeyUID), zap.String("imageName", parsed.ImageName), zap.Error(err)) 339 return 340 } 341 342 enlargeRatio := float64(identityImage.Width) / float64(parsed.BgSize) 343 344 if parsed.Ring { 345 account, err := multiaccountsDB.GetAccount(parsed.KeyUID) 346 if err != nil { 347 logger.Error("handleAccountImagesImpl: failed to GetAccount .", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) 348 return 349 } 350 351 accColorHash := account.ColorHash 352 353 if accColorHash == nil { 354 if parsed.PublicKey == "" { 355 logger.Error("handleAccountImagesImpl: no public key for color hash", zap.String("keyUid", parsed.KeyUID)) 356 } 357 358 accColorHash, err = colorhash.GenerateFor(parsed.PublicKey) 359 if err != nil { 360 logger.Error("handleAccountImagesImpl: could not generate color hash", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) 361 } 362 } 363 364 if accColorHash != nil { 365 payload, err = ring.DrawRing(&ring.DrawRingParam{ 366 Theme: parsed.Theme, ColorHash: accColorHash, ImageBytes: payload, Height: identityImage.Height, Width: identityImage.Width, RingWidth: parsed.RingWidth * enlargeRatio, 367 }) 368 if err != nil { 369 logger.Error("handleAccountImagesImpl: failed to draw ring for account identity", zap.Error(err)) 370 return 371 } 372 } 373 } 374 375 if parsed.IndicatorSize != 0 { 376 // enlarge indicator size based on identity image size / desired size 377 // or we get a bad quality identity image 378 payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize*enlargeRatio, parsed.IndicatorBorder*enlargeRatio, parsed.IndicatorCenterToEdge*enlargeRatio) 379 if err != nil { 380 logger.Error("handleAccountImagesImpl: failed to draw status-indicator for initials", zap.Error(err)) 381 return 382 } 383 } 384 385 if len(payload) == 0 { 386 logger.Error("handleAccountImagesImpl: empty image") 387 return 388 } 389 390 mime, err := images.GetProtobufImageMime(payload) 391 if err != nil { 392 logger.Error("failed to get mime", zap.Error(err)) 393 } 394 395 w.Header().Set("Content-Type", mime) 396 w.Header().Set("Cache-Control", "no-store") 397 398 _, err = w.Write(payload) 399 if err != nil { 400 logger.Error("handleAccountImagesImpl: failed to write image", zap.Error(err)) 401 } 402 } 403 404 func handleAccountImagesPlaceholder(logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { 405 if parsed.ImagePath == "" { 406 logger.Error("handleAccountImagesPlaceholder: no imagePath") 407 return 408 } 409 410 payload, im, err := images.ImageToBytesAndImage(parsed.ImagePath) 411 if err != nil { 412 logger.Error("handleAccountImagesPlaceholder: failed to load image from disk", zap.String("imageName", parsed.ImagePath)) 413 return 414 } 415 width := im.Bounds().Dx() 416 if parsed.BgSize == 0 { 417 parsed.BgSize = width 418 } 419 420 payload, err = images.RoundCrop(payload) 421 if err != nil { 422 logger.Error("handleAccountImagesPlaceholder: failed to crop image.", zap.String("imageName", parsed.ImagePath), zap.Error(err)) 423 return 424 } 425 426 if parsed.IndicatorSize != 0 { 427 enlargeIndicatorRatio := float64(width / parsed.BgSize) 428 payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize*enlargeIndicatorRatio, parsed.IndicatorBorder*enlargeIndicatorRatio, parsed.IndicatorCenterToEdge) 429 if err != nil { 430 logger.Error("handleAccountImagesPlaceholder: failed to draw status-indicator for initials", zap.Error(err)) 431 return 432 } 433 } 434 435 if len(payload) == 0 { 436 logger.Error("handleAccountImagesPlaceholder: empty image") 437 return 438 } 439 440 mime, err := images.GetProtobufImageMime(payload) 441 if err != nil { 442 logger.Error("failed to get mime", zap.Error(err)) 443 } 444 445 w.Header().Set("Content-Type", mime) 446 w.Header().Set("Cache-Control", "no-store") 447 448 _, err = w.Write(payload) 449 if err != nil { 450 logger.Error("handleAccountImagesPlaceholder: failed to write image", zap.Error(err)) 451 } 452 } 453 454 // handleAccountImages render multiaccounts custom profile image 455 func handleAccountImages(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc { 456 return func(w http.ResponseWriter, r *http.Request) { 457 params := r.URL.Query() 458 parsed := ParseImageParams(logger, params) 459 460 if parsed.KeyUID == "" { 461 handleAccountImagesPlaceholder(logger, w, parsed) 462 } else { 463 handleAccountImagesImpl(multiaccountsDB, logger, w, parsed) 464 } 465 } 466 } 467 468 func handleAccountInitialsImpl(multiaccountsDB *multiaccounts.Database, logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { 469 var name = parsed.FullName 470 var accColorHash multiaccounts.ColorHash 471 var account *multiaccounts.Account 472 473 if parsed.Ring && parsed.RingWidth == 0 { 474 logger.Error("handleAccountInitialsImpl: no ringWidth.") 475 return 476 } 477 478 if parsed.KeyUID != "" { 479 account, err := multiaccountsDB.GetAccount(parsed.KeyUID) 480 481 if err != nil { 482 logger.Error("handleAccountInitialsImpl: failed to get account.", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) 483 return 484 } 485 name = account.Name 486 accColorHash = account.ColorHash 487 } 488 489 initials := images.ExtractInitials(name, parsed.InitialsLength) 490 491 payload, err := images.GenerateInitialsImage(initials, parsed.BgColor, parsed.Color, parsed.FontFile, parsed.BgSize, parsed.FontSize, parsed.UppercaseRatio) 492 493 if err != nil { 494 logger.Error("handleAccountInitialsImpl: failed to generate initials image.", zap.String("keyUid", parsed.KeyUID), zap.String("name", account.Name), zap.Error(err)) 495 return 496 } 497 498 if parsed.Ring { 499 if accColorHash == nil { 500 if parsed.PublicKey == "" { 501 logger.Error("handleAccountInitialsImpl: no public key, can't draw ring", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) 502 } 503 504 accColorHash, err = colorhash.GenerateFor(parsed.PublicKey) 505 if err != nil { 506 logger.Error("handleAccountInitialsImpl: failed to generate color hash from pubkey", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) 507 } 508 } 509 510 if accColorHash != nil { 511 payload, err = ring.DrawRing(&ring.DrawRingParam{ 512 Theme: parsed.Theme, ColorHash: accColorHash, ImageBytes: payload, Height: parsed.BgSize, Width: parsed.BgSize, RingWidth: parsed.RingWidth, 513 }) 514 515 if err != nil { 516 logger.Error("handleAccountInitialsImpl: failed to draw ring for account identity", zap.Error(err)) 517 return 518 } 519 } 520 } 521 522 if parsed.IndicatorSize != 0 { 523 payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize, parsed.IndicatorBorder, parsed.IndicatorCenterToEdge) 524 if err != nil { 525 logger.Error("failed to draw status-indicator for initials", zap.Error(err)) 526 return 527 } 528 } 529 530 if len(payload) == 0 { 531 logger.Error("handleAccountInitialsImpl: empty image", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) 532 return 533 } 534 mime, err := images.GetProtobufImageMime(payload) 535 if err != nil { 536 logger.Error("failed to get mime", zap.Error(err)) 537 } 538 539 w.Header().Set("Content-Type", mime) 540 w.Header().Set("Cache-Control", "no-store") 541 542 _, err = w.Write(payload) 543 if err != nil { 544 logger.Error("failed to write image", zap.Error(err)) 545 } 546 } 547 548 func handleAccountInitialsPlaceholder(logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { 549 if parsed.FullName == "" { 550 logger.Error("handleAccountInitialsPlaceholder: no full name") 551 return 552 } 553 554 initials := images.ExtractInitials(parsed.FullName, parsed.InitialsLength) 555 556 payload, err := images.GenerateInitialsImage(initials, parsed.BgColor, parsed.Color, parsed.FontFile, parsed.BgSize, parsed.FontSize, parsed.UppercaseRatio) 557 558 if err != nil { 559 logger.Error("handleAccountInitialsPlaceholder: failed to generate initials image.", zap.String("keyUid", parsed.KeyUID), zap.String("name", parsed.FullName), zap.Error(err)) 560 return 561 } 562 563 if parsed.IndicatorSize != 0 { 564 payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize, parsed.IndicatorBorder, parsed.IndicatorCenterToEdge) 565 if err != nil { 566 logger.Error("failed to draw status-indicator for initials", zap.Error(err)) 567 return 568 } 569 } 570 571 if len(payload) == 0 { 572 logger.Error("handleAccountInitialsPlaceholder: empty image", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) 573 return 574 } 575 mime, err := images.GetProtobufImageMime(payload) 576 if err != nil { 577 logger.Error("failed to get mime", zap.Error(err)) 578 } 579 580 w.Header().Set("Content-Type", mime) 581 w.Header().Set("Cache-Control", "no-store") 582 583 _, err = w.Write(payload) 584 if err != nil { 585 logger.Error("failed to write image", zap.Error(err)) 586 } 587 } 588 589 // handleAccountInitials render multiaccounts/contacts initials avatar image 590 func handleAccountInitials(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc { 591 return func(w http.ResponseWriter, r *http.Request) { 592 params := r.URL.Query() 593 parsed := ParseImageParams(logger, params) 594 595 if parsed.FontFile == "" { 596 logger.Error("handleAccountInitials: no fontFile") 597 return 598 } 599 if parsed.FontSize == 0 { 600 logger.Error("handleAccountInitials: no fontSize") 601 return 602 } 603 if parsed.Color == color.Transparent { 604 logger.Error("handleAccountInitials: no color") 605 return 606 } 607 if parsed.BgSize == 0 { 608 logger.Error("handleAccountInitials: no size") 609 return 610 } 611 if parsed.BgColor == color.Transparent { 612 logger.Error("handleAccountInitials: no bgColor") 613 return 614 } 615 616 if parsed.KeyUID == "" && parsed.PublicKey == "" { 617 handleAccountInitialsPlaceholder(logger, w, parsed) 618 } else { 619 handleAccountInitialsImpl(multiaccountsDB, logger, w, parsed) 620 } 621 } 622 } 623 624 // handleContactImages render contacts custom profile image 625 func handleContactImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 626 if db == nil { 627 return handleRequestDBMissing(logger) 628 } 629 630 return func(w http.ResponseWriter, r *http.Request) { 631 params := r.URL.Query() 632 parsed := ParseImageParams(logger, params) 633 634 if parsed.PublicKey == "" { 635 logger.Error("no publicKey") 636 return 637 } 638 639 if parsed.ImageName == "" { 640 logger.Error("no imageName") 641 return 642 } 643 644 if parsed.Ring && parsed.RingWidth == 0 { 645 logger.Error("handleContactImages: no ringWidth.") 646 return 647 } 648 649 var payload []byte 650 err := db.QueryRow(`SELECT payload FROM chat_identity_contacts WHERE contact_id = ? and image_type = ?`, parsed.PublicKey, parsed.ImageName).Scan(&payload) 651 if err != nil { 652 logger.Error("failed to load image.", zap.String("contact id", parsed.PublicKey), zap.String("image type", parsed.ImageName), zap.Error(err)) 653 return 654 } 655 656 img, _, err := image.Decode(bytes.NewReader(payload)) 657 if err != nil { 658 logger.Error("failed to decode config.", zap.String("contact id", parsed.PublicKey), zap.String("image type", parsed.ImageName), zap.Error(err)) 659 return 660 } 661 width := img.Bounds().Dx() 662 663 if parsed.BgSize == 0 { 664 parsed.BgSize = width 665 } 666 667 payload, err = images.RoundCrop(payload) 668 if err != nil { 669 logger.Error("handleContactImages: failed to crop image.", zap.Error(err)) 670 return 671 } 672 673 enlargeRatio := float64(width) / float64(parsed.BgSize) 674 675 if parsed.Ring { 676 colorHash, err := colorhash.GenerateFor(parsed.PublicKey) 677 if err != nil { 678 logger.Error("could not generate color hash") 679 return 680 } 681 682 payload, err = ring.DrawRing(&ring.DrawRingParam{ 683 Theme: parsed.Theme, ColorHash: colorHash, ImageBytes: payload, Height: width, Width: width, RingWidth: parsed.RingWidth * enlargeRatio, 684 }) 685 686 if err != nil { 687 logger.Error("failed to draw ring for contact image.", zap.Error(err)) 688 return 689 } 690 } 691 692 if parsed.IndicatorSize != 0 { 693 payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize*enlargeRatio, parsed.IndicatorBorder*enlargeRatio, parsed.IndicatorCenterToEdge*enlargeRatio) 694 if err != nil { 695 logger.Error("handleContactImages: failed to draw status-indicator for initials", zap.Error(err)) 696 return 697 } 698 } 699 700 if len(payload) == 0 { 701 logger.Error("empty image") 702 return 703 } 704 mime, err := images.GetProtobufImageMime(payload) 705 if err != nil { 706 logger.Error("failed to get mime", zap.Error(err)) 707 } 708 709 w.Header().Set("Content-Type", mime) 710 w.Header().Set("Cache-Control", "no-store") 711 712 _, err = w.Write(payload) 713 if err != nil { 714 logger.Error("failed to write image", zap.Error(err)) 715 } 716 } 717 } 718 719 func ringEnabled(params url.Values) bool { 720 addRings, ok := params["addRing"] 721 return ok && len(addRings) == 1 && addRings[0] == "1" 722 } 723 724 func getTheme(params url.Values, logger *zap.Logger) ring.Theme { 725 theme := ring.LightTheme // default 726 themes, ok := params["theme"] 727 if ok && len(themes) > 0 { 728 t, err := strconv.Atoi(themes[0]) 729 if err != nil { 730 logger.Error("invalid param[theme], value: " + themes[0]) 731 } else { 732 theme = ring.Theme(t) 733 } 734 } 735 return theme 736 } 737 738 func handleDiscordAuthorAvatar(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 739 if db == nil { 740 return handleRequestDBMissing(logger) 741 } 742 743 return func(w http.ResponseWriter, r *http.Request) { 744 params := r.URL.Query() 745 parsed := ParseImageParams(logger, params) 746 747 if parsed.AuthorID == "" { 748 logger.Error("no authorIDs") 749 return 750 } 751 752 var image []byte 753 err := db.QueryRow(`SELECT avatar_image_payload FROM discord_message_authors WHERE id = ?`, parsed.AuthorID).Scan(&image) 754 if err != nil { 755 logger.Error("failed to find image", zap.Error(err)) 756 return 757 } 758 if len(image) == 0 { 759 logger.Error("empty image") 760 return 761 } 762 mime, err := images.GetProtobufImageMime(image) 763 if err != nil { 764 logger.Error("failed to get mime", zap.Error(err)) 765 } 766 767 w.Header().Set("Content-Type", mime) 768 w.Header().Set("Cache-Control", "no-store") 769 770 _, err = w.Write(image) 771 if err != nil { 772 logger.Error("failed to write image", zap.Error(err)) 773 } 774 } 775 } 776 777 func handleDiscordAttachment(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 778 if db == nil { 779 return handleRequestDBMissing(logger) 780 } 781 782 return func(w http.ResponseWriter, r *http.Request) { 783 params := r.URL.Query() 784 parsed := ParseImageParams(logger, params) 785 786 if parsed.MessageID == "" { 787 logger.Error("no messageID") 788 return 789 } 790 if parsed.AttachmentID == "" { 791 logger.Error("no attachmentID") 792 return 793 } 794 795 var image []byte 796 err := db.QueryRow(`SELECT payload FROM discord_message_attachments WHERE discord_message_id = ? AND id = ?`, parsed.MessageID, parsed.AttachmentID).Scan(&image) 797 if err != nil { 798 logger.Error("failed to find image", zap.Error(err)) 799 return 800 } 801 if len(image) == 0 { 802 logger.Error("empty image") 803 return 804 } 805 mime, err := images.GetProtobufImageMime(image) 806 if err != nil { 807 logger.Error("failed to get mime", zap.Error(err)) 808 } 809 810 w.Header().Set("Content-Type", mime) 811 w.Header().Set("Cache-Control", "no-store") 812 813 _, err = w.Write(image) 814 if err != nil { 815 logger.Error("failed to write image", zap.Error(err)) 816 } 817 } 818 } 819 820 func handleImage(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 821 if db == nil { 822 return handleRequestDBMissing(logger) 823 } 824 825 return func(w http.ResponseWriter, r *http.Request) { 826 params := r.URL.Query() 827 parsed := ParseImageParams(logger, params) 828 829 if parsed.MessageID == "" { 830 logger.Error("no messageID") 831 return 832 } 833 834 var image []byte 835 err := db.QueryRow(`SELECT image_payload FROM user_messages WHERE id = ?`, parsed.MessageID).Scan(&image) 836 if err != nil { 837 logger.Error("failed to find image", zap.Error(err)) 838 return 839 } 840 if len(image) == 0 { 841 logger.Error("empty image") 842 return 843 } 844 mime, err := images.GetProtobufImageMime(image) 845 if err != nil { 846 logger.Error("failed to get mime", zap.Error(err)) 847 } 848 849 w.Header().Set("Content-Type", mime) 850 w.Header().Set("Cache-Control", "no-store") 851 852 _, err = w.Write(image) 853 if err != nil { 854 logger.Error("failed to write image", zap.Error(err)) 855 } 856 } 857 } 858 859 func handleAudio(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 860 if db == nil { 861 return handleRequestDBMissing(logger) 862 } 863 864 return func(w http.ResponseWriter, r *http.Request) { 865 params := r.URL.Query() 866 parsed := ParseImageParams(logger, params) 867 868 if parsed.MessageID == "" { 869 logger.Error("no messageID") 870 return 871 } 872 873 var audio []byte 874 err := db.QueryRow(`SELECT audio_payload FROM user_messages WHERE id = ?`, parsed.MessageID).Scan(&audio) 875 if err != nil { 876 logger.Error("failed to find image", zap.Error(err)) 877 return 878 } 879 if len(audio) == 0 { 880 logger.Error("empty audio") 881 return 882 } 883 884 w.Header().Set("Content-Type", "audio/aac") 885 w.Header().Set("Cache-Control", "no-store") 886 887 _, err = w.Write(audio) 888 if err != nil { 889 logger.Error("failed to write audio", zap.Error(err)) 890 } 891 } 892 } 893 894 func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) http.HandlerFunc { 895 if downloader == nil { 896 return handleRequestDownloaderMissing(logger) 897 } 898 899 return func(w http.ResponseWriter, r *http.Request) { 900 params := r.URL.Query() 901 parsed := ParseImageParams(logger, params) 902 903 if parsed.Hash == "" { 904 logger.Error("no hash") 905 return 906 } 907 908 content, err := downloader.Get(parsed.Hash, parsed.Download) 909 if err != nil { 910 logger.Error("could not download hash", zap.Error(err)) 911 return 912 } 913 914 w.Header().Set("Cache-Control", "max-age:290304000, public") 915 w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat)) 916 917 _, err = w.Write(content) 918 if err != nil { 919 logger.Error("failed to write ipfs resource", zap.Error(err)) 920 } 921 } 922 } 923 924 func handleQRCodeGeneration(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc { 925 return func(w http.ResponseWriter, r *http.Request) { 926 params := r.URL.Query() 927 928 payload := generateQRBytes(params, logger, multiaccountsDB) 929 mime, err := images.GetProtobufImageMime(payload) 930 931 if err != nil { 932 logger.Error("could not generate image from payload", zap.Error(err)) 933 } 934 935 w.Header().Set("Content-Type", mime) 936 w.Header().Set("Cache-Control", "no-store") 937 938 _, err = w.Write(payload) 939 940 if err != nil { 941 logger.Error("failed to write image", zap.Error(err)) 942 } 943 } 944 } 945 946 func handleCommunityTokenImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 947 if db == nil { 948 return handleRequestDBMissing(logger) 949 } 950 951 return func(w http.ResponseWriter, r *http.Request) { 952 params := r.URL.Query() 953 954 if len(params["communityID"]) == 0 { 955 logger.Error("no communityID") 956 return 957 } 958 if len(params["chainID"]) == 0 { 959 logger.Error("no chainID") 960 return 961 } 962 if len(params["symbol"]) == 0 { 963 logger.Error("no symbol") 964 return 965 } 966 967 chainID, err := strconv.ParseUint(params["chainID"][0], 10, 64) 968 if err != nil { 969 logger.Error("invalid chainID in community token image", zap.Error(err)) 970 return 971 } 972 973 var base64Image string 974 err = db.QueryRow("SELECT image_base64 FROM community_tokens WHERE community_id = ? AND chain_id = ? AND symbol = ?", params["communityID"][0], chainID, params["symbol"][0]).Scan(&base64Image) 975 if err != nil { 976 logger.Error("failed to find community token image", zap.Error(err)) 977 return 978 } 979 if len(base64Image) == 0 { 980 logger.Error("empty community token image") 981 return 982 } 983 imagePayload, err := images.GetPayloadFromURI(base64Image) 984 if err != nil { 985 logger.Error("failed to get community token image payload", zap.Error(err)) 986 return 987 } 988 mime, err := images.GetProtobufImageMime(imagePayload) 989 if err != nil { 990 logger.Error("failed to get community token image mime", zap.Error(err)) 991 } 992 993 w.Header().Set("Content-Type", mime) 994 w.Header().Set("Cache-Control", "no-store") 995 996 _, err = w.Write(imagePayload) 997 if err != nil { 998 logger.Error("failed to write community token image", zap.Error(err)) 999 } 1000 } 1001 } 1002 1003 func handleCommunityDescriptionImagesPath(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 1004 if db == nil { 1005 return handleRequestDBMissing(logger) 1006 } 1007 1008 return func(w http.ResponseWriter, r *http.Request) { 1009 params := r.URL.Query() 1010 1011 if len(params["communityID"]) == 0 { 1012 logger.Error("[handleCommunityDescriptionImagesPath] no communityID") 1013 return 1014 } 1015 communityID := params["communityID"][0] 1016 1017 name := "" 1018 if len(params["name"]) > 0 { 1019 name = params["name"][0] 1020 } 1021 1022 err, communityDescription := getCommunityDescription(db, communityID, logger) 1023 if err != nil { 1024 return 1025 } 1026 if communityDescription.Identity == nil { 1027 logger.Error("no identity in community description", zap.String("community id", communityID)) 1028 return 1029 } 1030 1031 var imagePayload []byte 1032 for t, i := range communityDescription.Identity.Images { 1033 if t == name { 1034 imagePayload = i.Payload 1035 } 1036 } 1037 if imagePayload == nil { 1038 logger.Error("can't find community description image", zap.String("community id", communityID), zap.String("name", name)) 1039 return 1040 } 1041 1042 mime, err := images.GetProtobufImageMime(imagePayload) 1043 if err != nil { 1044 logger.Error("failed to get community image mime", zap.String("community id", communityID), zap.Error(err)) 1045 } 1046 1047 w.Header().Set("Content-Type", mime) 1048 w.Header().Set("Cache-Control", "no-store") 1049 _, err = w.Write(imagePayload) 1050 if err != nil { 1051 logger.Error("failed to write community image", zap.String("community id", communityID), zap.Error(err)) 1052 } 1053 } 1054 } 1055 1056 func handleCommunityDescriptionTokenImagesPath(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 1057 if db == nil { 1058 return handleRequestDBMissing(logger) 1059 } 1060 1061 return func(w http.ResponseWriter, r *http.Request) { 1062 params := r.URL.Query() 1063 1064 if len(params["communityID"]) == 0 { 1065 logger.Error("[handleCommunityDescriptionTokenImagesPath] no communityID") 1066 return 1067 } 1068 communityID := params["communityID"][0] 1069 1070 if len(params["symbol"]) == 0 { 1071 logger.Error("[handleCommunityDescriptionTokenImagesPath] no symbol") 1072 return 1073 } 1074 symbol := params["symbol"][0] 1075 1076 err, communityDescription := getCommunityDescription(db, communityID, logger) 1077 if err != nil { 1078 return 1079 } 1080 1081 var foundToken *protobuf.CommunityTokenMetadata 1082 for _, m := range communityDescription.CommunityTokensMetadata { 1083 if m.GetSymbol() == symbol { 1084 foundToken = m 1085 } 1086 } 1087 if foundToken == nil { 1088 logger.Error("can't find community description token image", zap.String("community id", communityID), zap.String("symbol", symbol)) 1089 return 1090 } 1091 1092 imagePayload, err := images.GetPayloadFromURI(foundToken.Image) 1093 if err != nil { 1094 logger.Error("failed to get community description token image payload", zap.Error(err)) 1095 return 1096 } 1097 mime, err := images.GetProtobufImageMime(imagePayload) 1098 if err != nil { 1099 logger.Error("failed to get community description token image mime", zap.String("community id", communityID), zap.String("symbol", symbol), zap.Error(err)) 1100 } 1101 1102 w.Header().Set("Content-Type", mime) 1103 w.Header().Set("Cache-Control", "no-store") 1104 _, err = w.Write(imagePayload) 1105 if err != nil { 1106 logger.Error("failed to write community description token image", zap.String("community id", communityID), zap.String("symbol", symbol), zap.Error(err)) 1107 } 1108 } 1109 } 1110 1111 // getCommunityDescription returns the latest community description from the cache. 1112 // NOTE: you should ensure preprocessDescription is called before this function. 1113 func getCommunityDescription(db *sql.DB, communityID string, logger *zap.Logger) (error, *protobuf.CommunityDescription) { 1114 var descriptionBytes []byte 1115 err := db.QueryRow(`SELECT description FROM encrypted_community_description_cache WHERE community_id = ? ORDER BY clock DESC LIMIT 1`, types.Hex2Bytes(communityID)).Scan(&descriptionBytes) 1116 if err != nil { 1117 logger.Error("failed to find community description", zap.String("community id", communityID), zap.Error(err)) 1118 return err, nil 1119 } 1120 communityDescription := new(protobuf.CommunityDescription) 1121 err = proto.Unmarshal(descriptionBytes, communityDescription) 1122 if err != nil { 1123 logger.Error("failed to unmarshal community description", zap.String("community id", communityID), zap.Error(err)) 1124 } 1125 return err, communityDescription 1126 } 1127 1128 func handleWalletCommunityImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 1129 if db == nil { 1130 return handleRequestDBMissing(logger) 1131 } 1132 1133 return func(w http.ResponseWriter, r *http.Request) { 1134 params := r.URL.Query() 1135 1136 if len(params["communityID"]) == 0 { 1137 logger.Error("no communityID") 1138 return 1139 } 1140 1141 var image []byte 1142 err := db.QueryRow(`SELECT image_payload FROM community_data_cache WHERE id = ?`, params["communityID"][0]).Scan(&image) 1143 if err != nil { 1144 logger.Error("failed to find wallet community image", zap.Error(err)) 1145 return 1146 } 1147 if len(image) == 0 { 1148 logger.Error("empty wallet community image") 1149 return 1150 } 1151 mime, err := images.GetProtobufImageMime(image) 1152 if err != nil { 1153 logger.Error("failed to get wallet community image mime", zap.Error(err)) 1154 } 1155 1156 w.Header().Set("Content-Type", mime) 1157 w.Header().Set("Cache-Control", "no-store") 1158 1159 _, err = w.Write(image) 1160 if err != nil { 1161 logger.Error("failed to write wallet community image", zap.Error(err)) 1162 } 1163 } 1164 } 1165 1166 func handleWalletCollectionImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 1167 if db == nil { 1168 return handleRequestDBMissing(logger) 1169 } 1170 1171 return func(w http.ResponseWriter, r *http.Request) { 1172 params := r.URL.Query() 1173 1174 if len(params["chainID"]) == 0 { 1175 logger.Error("no chainID") 1176 return 1177 } 1178 1179 if len(params["contractAddress"]) == 0 { 1180 logger.Error("no contractAddress") 1181 return 1182 } 1183 1184 chainID, err := strconv.ParseUint(params["chainID"][0], 10, 64) 1185 if err != nil { 1186 logger.Error("invalid chainID in wallet collectible image", zap.Error(err)) 1187 return 1188 } 1189 contractAddress := eth_common.HexToAddress(params["contractAddress"][0]) 1190 if len(contractAddress) == 0 { 1191 logger.Error("invalid contractAddress in wallet collectible image", zap.Error(err)) 1192 return 1193 } 1194 1195 var image []byte 1196 err = db.QueryRow(`SELECT image_payload FROM collection_data_cache WHERE chain_id = ? AND contract_address = ?`, 1197 chainID, 1198 contractAddress).Scan(&image) 1199 if err != nil { 1200 logger.Error("failed to find wallet collection image", zap.Error(err)) 1201 return 1202 } 1203 if len(image) == 0 { 1204 logger.Error("empty wallet collection image") 1205 return 1206 } 1207 mime, err := images.GetProtobufImageMime(image) 1208 if err != nil { 1209 logger.Error("failed to get wallet collection image mime", zap.Error(err)) 1210 } 1211 1212 w.Header().Set("Content-Type", mime) 1213 w.Header().Set("Cache-Control", "no-store") 1214 1215 _, err = w.Write(image) 1216 if err != nil { 1217 logger.Error("failed to write wallet collection image", zap.Error(err)) 1218 } 1219 } 1220 } 1221 1222 func handleWalletCollectibleImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { 1223 if db == nil { 1224 return handleRequestDBMissing(logger) 1225 } 1226 1227 return func(w http.ResponseWriter, r *http.Request) { 1228 params := r.URL.Query() 1229 1230 if len(params["chainID"]) == 0 { 1231 logger.Error("no chainID") 1232 return 1233 } 1234 1235 if len(params["contractAddress"]) == 0 { 1236 logger.Error("no contractAddress") 1237 return 1238 } 1239 1240 if len(params["tokenID"]) == 0 { 1241 logger.Error("no tokenID") 1242 return 1243 } 1244 1245 chainID, err := strconv.ParseUint(params["chainID"][0], 10, 64) 1246 if err != nil { 1247 logger.Error("invalid chainID in wallet collectible image", zap.Error(err)) 1248 return 1249 } 1250 contractAddress := eth_common.HexToAddress(params["contractAddress"][0]) 1251 if len(contractAddress) == 0 { 1252 logger.Error("invalid contractAddress in wallet collectible image", zap.Error(err)) 1253 return 1254 } 1255 tokenID, ok := big.NewInt(0).SetString(params["tokenID"][0], 10) 1256 if !ok { 1257 logger.Error("invalid tokenID in wallet collectible image", zap.Error(err)) 1258 return 1259 } 1260 1261 var image []byte 1262 err = db.QueryRow(`SELECT image_payload FROM collectible_data_cache WHERE chain_id = ? AND contract_address = ? AND token_id = ?`, 1263 chainID, 1264 contractAddress, 1265 (*bigint.SQLBigIntBytes)(tokenID)).Scan(&image) 1266 if err != nil { 1267 logger.Error("failed to find wallet collectible image", zap.Error(err)) 1268 return 1269 } 1270 if len(image) == 0 { 1271 logger.Error("empty image") 1272 return 1273 } 1274 mime, err := images.GetProtobufImageMime(image) 1275 if err != nil { 1276 logger.Error("failed to get wallet collectible image mime", zap.Error(err)) 1277 } 1278 1279 w.Header().Set("Content-Type", mime) 1280 w.Header().Set("Cache-Control", "no-store") 1281 1282 _, err = w.Write(image) 1283 if err != nil { 1284 logger.Error("failed to write wallet collectible image", zap.Error(err)) 1285 } 1286 } 1287 }