Re-organise the code to be more modular (#3172)
This commit is contained in:
parent
42ee98e295
commit
6ac2308ee1
|
@ -51,3 +51,4 @@ exclude_patterns:
|
|||
- "protocol/pushnotificationclient/migrations/migrations.go"
|
||||
- "protocol/pushnotificationserver/migrations/migrations.go"
|
||||
- "protocol/transport/migrations/migrations.go"
|
||||
- "images/qr-assets.go"
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
|
@ -152,3 +152,16 @@ func isWebp(buf []byte) bool {
|
|||
buf[8] == 0x57 && buf[9] == 0x45 &&
|
||||
buf[10] == 0x42 && buf[11] == 0x50
|
||||
}
|
||||
|
||||
func GetImageDimensions(imgBytes []byte) (int, int, error) {
|
||||
// Decode image bytes
|
||||
img, _, err := image.Decode(bytes.NewReader(imgBytes))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// Get the image dimensions
|
||||
bounds := img.Bounds()
|
||||
width := bounds.Max.X - bounds.Min.X
|
||||
height := bounds.Max.Y - bounds.Min.Y
|
||||
return width, height, nil
|
||||
}
|
||||
|
|
|
@ -1,16 +1,41 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/oliamb/cutter"
|
||||
"go.uber.org/zap"
|
||||
xdraw "golang.org/x/image/draw"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type Circle struct {
|
||||
X, Y, R int
|
||||
}
|
||||
|
||||
func (c *Circle) ColorModel() color.Model {
|
||||
return color.AlphaModel
|
||||
}
|
||||
func (c *Circle) Bounds() image.Rectangle {
|
||||
return image.Rect(c.X-c.R, c.Y-c.R, c.X+c.R, c.Y+c.R)
|
||||
}
|
||||
func (c *Circle) At(x, y int) color.Color {
|
||||
xx, yy, rr := float64(x-c.X)+0.5, float64(y-c.Y)+0.5, float64(c.R)
|
||||
if xx*xx+yy*yy < rr*rr {
|
||||
return color.Alpha{255}
|
||||
}
|
||||
return color.Alpha{0}
|
||||
}
|
||||
|
||||
func Resize(size ResizeDimension, img image.Image) image.Image {
|
||||
var width, height uint
|
||||
|
||||
|
@ -84,3 +109,122 @@ func CropCenter(img image.Image) (image.Image, error) {
|
|||
}
|
||||
return Crop(img, cropRect)
|
||||
}
|
||||
|
||||
func ImageToBytes(imagePath string) ([]byte, error) {
|
||||
// Open the image file
|
||||
file, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Decode the image
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new buffer to hold the image data
|
||||
var imgBuffer bytes.Buffer
|
||||
|
||||
// Encode the image to the desired format and save it in the buffer
|
||||
err = png.Encode(&imgBuffer, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the image data as a byte slice
|
||||
return imgBuffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func AddPadding(img image.Image, padding int) *image.RGBA {
|
||||
bounds := img.Bounds()
|
||||
newBounds := image.Rect(bounds.Min.X-padding, bounds.Min.Y-padding, bounds.Max.X+padding, bounds.Max.Y+padding)
|
||||
paddedImg := image.NewRGBA(newBounds)
|
||||
draw.Draw(paddedImg, newBounds, &image.Uniform{C: color.White}, image.ZP, draw.Src)
|
||||
|
||||
return paddedImg
|
||||
}
|
||||
|
||||
func EncodePNG(img *image.RGBA) ([]byte, error) {
|
||||
resultImg := &bytes.Buffer{}
|
||||
err := png.Encode(resultImg, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resultImg.Bytes(), nil
|
||||
}
|
||||
|
||||
func CreateCircle(img image.Image) *image.RGBA {
|
||||
bounds := img.Bounds()
|
||||
circle := image.NewRGBA(bounds)
|
||||
draw.DrawMask(circle, bounds, img, image.ZP, &Circle{
|
||||
X: bounds.Dx() / 2,
|
||||
Y: bounds.Dy() / 2,
|
||||
R: bounds.Dx() / 2,
|
||||
}, image.ZP, draw.Over)
|
||||
return circle
|
||||
}
|
||||
|
||||
func PlaceCircleInCenter(paddedImg, circle *image.RGBA) *image.RGBA {
|
||||
bounds := circle.Bounds()
|
||||
centerX := (paddedImg.Bounds().Min.X + paddedImg.Bounds().Max.X) / 2
|
||||
centerY := (paddedImg.Bounds().Min.Y + paddedImg.Bounds().Max.Y) / 2
|
||||
draw.Draw(paddedImg, bounds.Add(image.Pt(centerX-bounds.Dx()/2, centerY-bounds.Dy()/2)), circle, image.ZP, draw.Over)
|
||||
return paddedImg
|
||||
}
|
||||
|
||||
func ResizeImage(imgBytes []byte, width, height int) ([]byte, error) {
|
||||
// Decode image bytes
|
||||
img, _, err := image.Decode(bytes.NewReader(imgBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new image with the desired dimensions
|
||||
newImg := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
xdraw.BiLinear.Scale(newImg, newImg.Bounds(), img, img.Bounds(), draw.Over, nil)
|
||||
// Encode the new image to bytes
|
||||
var newImgBytes bytes.Buffer
|
||||
if err = png.Encode(&newImgBytes, newImg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newImgBytes.Bytes(), nil
|
||||
}
|
||||
|
||||
func SuperimposeLogoOnQRImage(imageBytes []byte, qrFilepath []byte) []byte {
|
||||
// Read the two images from bytes
|
||||
img1, _, err := image.Decode(bytes.NewReader(imageBytes))
|
||||
|
||||
if err != nil {
|
||||
log.Error("error decoding logo Image", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
img2, _, err := image.Decode(bytes.NewReader(qrFilepath))
|
||||
|
||||
if err != nil {
|
||||
log.Error("error decoding QR Image", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
// Create a new image with the dimensions of the first image
|
||||
result := image.NewRGBA(img1.Bounds())
|
||||
// Draw the first image on the new image
|
||||
draw.Draw(result, img1.Bounds(), img1, image.ZP, draw.Src)
|
||||
// Get the dimensions of the second image
|
||||
img2Bounds := img2.Bounds()
|
||||
// Calculate the x and y coordinates to center the second image
|
||||
x := (img1.Bounds().Dx() - img2Bounds.Dx()) / 2
|
||||
y := (img1.Bounds().Dy() - img2Bounds.Dy()) / 2
|
||||
// Draw the second image on top of the first image at the calculated coordinates
|
||||
draw.Draw(result, img2Bounds.Add(image.Pt(x, y)), img2, image.ZP, draw.Over)
|
||||
// Encode the final image to a desired format
|
||||
var b bytes.Buffer
|
||||
err = png.Encode(&b, result)
|
||||
|
||||
if err != nil {
|
||||
log.Error("error encoding final result Image to Buffer", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.Bytes()
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,11 +2,12 @@ package images
|
|||
|
||||
// Test data that would typically only exist in a test file, used for exporting sample data outside the package.
|
||||
var (
|
||||
testJpegBytes = []byte{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50}
|
||||
testPngBytes = []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48}
|
||||
testGifBytes = []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x01, 0x00, 0x01, 0x84, 0x1f, 0x00, 0xff}
|
||||
testWebpBytes = []byte{0x52, 0x49, 0x46, 0x46, 0x90, 0x49, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50}
|
||||
testAacBytes = []byte{0xff, 0xf1, 0x50, 0x80, 0x1c, 0x3f, 0xfc, 0xda, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35}
|
||||
testJpegBytes = []byte{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50}
|
||||
testPngBytes = []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48}
|
||||
testGifBytes = []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x01, 0x00, 0x01, 0x84, 0x1f, 0x00, 0xff}
|
||||
testWebpBytes = []byte{0x52, 0x49, 0x46, 0x46, 0x90, 0x49, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50}
|
||||
testAacBytes = []byte{0xff, 0xf1, 0x50, 0x80, 0x1c, 0x3f, 0xfc, 0xda, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35}
|
||||
testLogoBytes, _ = Asset("_assets/tests/qr/status.png")
|
||||
)
|
||||
|
||||
func SampleIdentityImages() []IdentityImage {
|
||||
|
@ -31,3 +32,17 @@ func SampleIdentityImages() []IdentityImage {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func SampleIdentityImageForQRCode() []IdentityImage {
|
||||
return []IdentityImage{
|
||||
{
|
||||
Name: LargeDimName,
|
||||
Payload: testLogoBytes,
|
||||
Width: 240,
|
||||
Height: 300,
|
||||
FileSize: 1024,
|
||||
ResizeTarget: 240,
|
||||
Clock: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
182
qrcode/qrcode.go
182
qrcode/qrcode.go
|
@ -1,182 +0,0 @@
|
|||
package qrcode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
xdraw "golang.org/x/image/draw"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPadding = 20
|
||||
)
|
||||
|
||||
func GetImageDimensions(imgBytes []byte) (int, int, error) {
|
||||
// Decode image bytes
|
||||
img, _, err := image.Decode(bytes.NewReader(imgBytes))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// Get the image dimensions
|
||||
bounds := img.Bounds()
|
||||
width := bounds.Max.X - bounds.Min.X
|
||||
height := bounds.Max.Y - bounds.Min.Y
|
||||
return width, height, nil
|
||||
}
|
||||
func ToLogoImageFromBytes(imageBytes []byte, padding int) []byte {
|
||||
img, _, err := image.Decode(bytes.NewReader(imageBytes))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bounds := img.Bounds()
|
||||
newBounds := image.Rect(bounds.Min.X-padding, bounds.Min.Y-padding, bounds.Max.X+padding, bounds.Max.Y+padding)
|
||||
white := image.NewRGBA(newBounds)
|
||||
draw.Draw(white, newBounds, &image.Uniform{C: color.White}, image.ZP, draw.Src)
|
||||
// Create a circular mask
|
||||
circle := image.NewRGBA(bounds)
|
||||
draw.DrawMask(circle, bounds, img, image.ZP, &Circle{
|
||||
X: bounds.Dx() / 2,
|
||||
Y: bounds.Dy() / 2,
|
||||
R: bounds.Dx() / 2,
|
||||
}, image.ZP, draw.Over)
|
||||
// Calculate the center point of the new image
|
||||
centerX := (newBounds.Min.X + newBounds.Max.X) / 2
|
||||
centerY := (newBounds.Min.Y + newBounds.Max.Y) / 2
|
||||
// Draw the circular image in the center of the new image
|
||||
draw.Draw(white, bounds.Add(image.Pt(centerX-bounds.Dx()/2, centerY-bounds.Dy()/2)), circle, image.ZP, draw.Over)
|
||||
// Encode image to png format and save in a bytes
|
||||
var resultImg bytes.Buffer
|
||||
err = png.Encode(&resultImg, white)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
resultBytes := resultImg.Bytes()
|
||||
return resultBytes
|
||||
}
|
||||
func SuperimposeImage(imageBytes []byte, qrFilepath []byte) []byte {
|
||||
// Read the two images from bytes
|
||||
img1, _, _ := image.Decode(bytes.NewReader(imageBytes))
|
||||
img2, _, _ := image.Decode(bytes.NewReader(qrFilepath))
|
||||
// Create a new image with the dimensions of the first image
|
||||
result := image.NewRGBA(img1.Bounds())
|
||||
// Draw the first image on the new image
|
||||
draw.Draw(result, img1.Bounds(), img1, image.ZP, draw.Src)
|
||||
// Get the dimensions of the second image
|
||||
img2Bounds := img2.Bounds()
|
||||
// Calculate the x and y coordinates to center the second image
|
||||
x := (img1.Bounds().Dx() - img2Bounds.Dx()) / 2
|
||||
y := (img1.Bounds().Dy() - img2Bounds.Dy()) / 2
|
||||
// Draw the second image on top of the first image at the calculated coordinates
|
||||
draw.Draw(result, img2Bounds.Add(image.Pt(x, y)), img2, image.ZP, draw.Over)
|
||||
// Encode the final image to a desired format
|
||||
var b bytes.Buffer
|
||||
if err := png.Encode(&b, result); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
func ResizeImage(imgBytes []byte, width, height int) ([]byte, error) {
|
||||
// Decode image bytes
|
||||
img, _, err := image.Decode(bytes.NewReader(imgBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new image with the desired dimensions
|
||||
newImg := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
xdraw.BiLinear.Scale(newImg, newImg.Bounds(), img, img.Bounds(), draw.Over, nil)
|
||||
// Encode the new image to bytes
|
||||
var newImgBytes bytes.Buffer
|
||||
if err = png.Encode(&newImgBytes, newImg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newImgBytes.Bytes(), nil
|
||||
}
|
||||
|
||||
func ImageToBytes(imagePath string) ([]byte, error) {
|
||||
// Open the image file
|
||||
file, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Decode the image
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new buffer to hold the image data
|
||||
var imgBuffer bytes.Buffer
|
||||
|
||||
// Encode the image to the desired format and save it in the buffer
|
||||
err = png.Encode(&imgBuffer, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the image data as a byte slice
|
||||
return imgBuffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func GetLogoImage(multiaccountsDB *multiaccounts.Database, params url.Values) ([]byte, error) {
|
||||
var imageFolderBasePath = "../_assets/tests/"
|
||||
var logoFileStaticPath = imageFolderBasePath + "status.png"
|
||||
|
||||
keyUids, ok := params["keyUid"]
|
||||
if !ok || len(keyUids) == 0 {
|
||||
return nil, errors.New("no keyUid")
|
||||
}
|
||||
imageNames, ok := params["imageName"]
|
||||
if !ok || len(imageNames) == 0 {
|
||||
return nil, errors.New("no imageName")
|
||||
}
|
||||
identityImageObjectFromDB, err := multiaccountsDB.GetIdentityImage(keyUids[0], imageNames[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
staticLogoFileBytes, _ := ImageToBytes(logoFileStaticPath)
|
||||
|
||||
if identityImageObjectFromDB == nil {
|
||||
return ToLogoImageFromBytes(staticLogoFileBytes, GetPadding(staticLogoFileBytes)), nil
|
||||
}
|
||||
|
||||
return ToLogoImageFromBytes(identityImageObjectFromDB.Payload, GetPadding(identityImageObjectFromDB.Payload)), nil
|
||||
|
||||
}
|
||||
|
||||
func GetPadding(imgBytes []byte) int {
|
||||
size, _, err := GetImageDimensions(imgBytes)
|
||||
if err != nil {
|
||||
return defaultPadding
|
||||
}
|
||||
return size / 5
|
||||
}
|
||||
|
||||
type Circle struct {
|
||||
X, Y, R int
|
||||
}
|
||||
|
||||
func (c *Circle) ColorModel() color.Model {
|
||||
return color.AlphaModel
|
||||
}
|
||||
func (c *Circle) Bounds() image.Rectangle {
|
||||
return image.Rect(c.X-c.R, c.Y-c.R, c.X+c.R, c.Y+c.R)
|
||||
}
|
||||
func (c *Circle) At(x, y int) color.Color {
|
||||
xx, yy, rr := float64(x-c.X)+0.5, float64(y-c.Y)+0.5, float64(c.R)
|
||||
if xx*xx+yy*yy < rr*rr {
|
||||
return color.Alpha{255}
|
||||
}
|
||||
return color.Alpha{0}
|
||||
}
|
|
@ -3,15 +3,12 @@ package server
|
|||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"image"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
qrcode "github.com/yeqown/go-qrcode/v2"
|
||||
"github.com/yeqown/go-qrcode/writer/standard"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/images"
|
||||
|
@ -20,7 +17,6 @@ import (
|
|||
"github.com/status-im/status-go/protocol/identity/colorhash"
|
||||
"github.com/status-im/status-go/protocol/identity/identicon"
|
||||
"github.com/status-im/status-go/protocol/identity/ring"
|
||||
qrcodeutils "github.com/status-im/status-go/qrcode"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -40,25 +36,6 @@ const (
|
|||
|
||||
type HandlerPatternMap map[string]http.HandlerFunc
|
||||
|
||||
type QROptions struct {
|
||||
URL string `json:"url"`
|
||||
ErrorCorrectionLevel string `json:"errorCorrectionLevel"`
|
||||
Capacity string `json:"capacity"`
|
||||
AllowProfileImage bool `json:"withLogo"`
|
||||
}
|
||||
|
||||
type WriterCloserByteBuffer struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (wc WriterCloserByteBuffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriterCloserByteBuffer() *WriterCloserByteBuffer {
|
||||
return &WriterCloserByteBuffer{bytes.NewBuffer([]byte{})}
|
||||
}
|
||||
|
||||
func handleAccountImages(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
params := r.URL.Query()
|
||||
|
@ -412,62 +389,19 @@ func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) http.HandlerFun
|
|||
func handleQRCodeGeneration(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
params := r.URL.Query()
|
||||
qrURLBase64Encoded, ok := params["qrurl"]
|
||||
if !ok || len(qrURLBase64Encoded) == 0 {
|
||||
logger.Error("no qr url provided")
|
||||
return
|
||||
}
|
||||
qrURLBase64Decoded, err := base64.StdEncoding.DecodeString(qrURLBase64Encoded[0])
|
||||
if err != nil {
|
||||
logger.Error("error decoding string from base64", zap.Error(err))
|
||||
}
|
||||
level, ok := params["level"]
|
||||
// Default error correction level
|
||||
correctionLevel := qrcode.ErrorCorrectionMedium
|
||||
if ok && len(level) == 1 {
|
||||
switch level[0] {
|
||||
case "4":
|
||||
correctionLevel = qrcode.ErrorCorrectionHighest
|
||||
case "1":
|
||||
correctionLevel = qrcode.ErrorCorrectionLow
|
||||
case "3":
|
||||
correctionLevel = qrcode.ErrorCorrectionQuart
|
||||
}
|
||||
}
|
||||
buf := NewWriterCloserByteBuffer()
|
||||
qrc, err := qrcode.NewWith(string(qrURLBase64Decoded),
|
||||
qrcode.WithEncodingMode(qrcode.EncModeAuto),
|
||||
qrcode.WithErrorCorrectionLevel(correctionLevel),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("could not generate QRCode", zap.Error(err))
|
||||
}
|
||||
nw := standard.NewWithWriter(buf)
|
||||
if err = qrc.Save(nw); err != nil {
|
||||
logger.Error("could not save image", zap.Error(err))
|
||||
}
|
||||
payload := buf.Bytes()
|
||||
logo, err := qrcodeutils.GetLogoImage(multiaccountsDB, params)
|
||||
if err == nil {
|
||||
qrWidth, qrHeight, _ := qrcodeutils.GetImageDimensions(payload)
|
||||
logo, _ = qrcodeutils.ResizeImage(logo, qrWidth/5, qrHeight/5)
|
||||
payload = qrcodeutils.SuperimposeImage(payload, logo)
|
||||
}
|
||||
size, ok := params["size"]
|
||||
if ok && len(size) == 1 {
|
||||
size, err := strconv.Atoi(size[0])
|
||||
if err == nil {
|
||||
payload, _ = qrcodeutils.ResizeImage(payload, size, size)
|
||||
}
|
||||
}
|
||||
|
||||
payload := generateQRBytes(params, logger, multiaccountsDB)
|
||||
mime, err := images.GetProtobufImageMime(payload)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not generate image from payload", zap.Error(err))
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", mime)
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
|
||||
_, err = w.Write(payload)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("failed to write image", zap.Error(err))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/yeqown/go-qrcode/v2"
|
||||
"github.com/yeqown/go-qrcode/writer/standard"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
)
|
||||
|
||||
type WriterCloserByteBuffer struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (wc WriterCloserByteBuffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriterCloserByteBuffer() *WriterCloserByteBuffer {
|
||||
return &WriterCloserByteBuffer{bytes.NewBuffer([]byte{})}
|
||||
}
|
||||
|
||||
type QRConfig struct {
|
||||
DecodedQRURL string
|
||||
WithLogo bool
|
||||
CorrectionLevel qrcode.EncodeOption
|
||||
KeyUID string
|
||||
ImageName string
|
||||
Size int
|
||||
Params url.Values
|
||||
}
|
||||
|
||||
func NewQRConfig(params url.Values, logger *zap.Logger) (*QRConfig, error) {
|
||||
config := &QRConfig{}
|
||||
config.Params = params
|
||||
err := config.setQrURL()
|
||||
|
||||
if err != nil {
|
||||
logger.Error("[qrops-error] error in setting QRURL", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.setAllowProfileImage()
|
||||
config.setErrorCorrectionLevel()
|
||||
err = config.setSize()
|
||||
|
||||
if err != nil {
|
||||
logger.Error("[qrops-error] could not convert string to int for size param ", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.WithLogo {
|
||||
err = config.setKeyUID()
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.setImageName()
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (q *QRConfig) setQrURL() error {
|
||||
qrURL, ok := q.Params["url"]
|
||||
|
||||
if !ok || len(qrURL) == 0 {
|
||||
return errors.New("[qrops-error] no qr url provided")
|
||||
}
|
||||
|
||||
decodedURL, err := base64.StdEncoding.DecodeString(qrURL[0])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.DecodedQRURL = string(decodedURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QRConfig) setAllowProfileImage() {
|
||||
allowProfileImage, ok := q.Params["allowProfileImage"]
|
||||
|
||||
if !ok || len(allowProfileImage) == 0 {
|
||||
// we default to false when this flag was not provided
|
||||
// so someone does not want to allowProfileImage on their QR Image
|
||||
// fine then :)
|
||||
q.WithLogo = false
|
||||
}
|
||||
|
||||
LogoOnImage, err := strconv.ParseBool(allowProfileImage[0])
|
||||
|
||||
if err != nil {
|
||||
// maybe for fun someone tries to send non-boolean values to this flag
|
||||
// we also default to false in that case
|
||||
q.WithLogo = false
|
||||
}
|
||||
|
||||
// if we reach here its most probably true
|
||||
q.WithLogo = LogoOnImage
|
||||
}
|
||||
|
||||
func (q *QRConfig) setErrorCorrectionLevel() {
|
||||
level, ok := q.Params["level"]
|
||||
if !ok || len(level) == 0 {
|
||||
// we default to MediumLevel of error correction when the level flag
|
||||
// is not passed.
|
||||
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
|
||||
}
|
||||
|
||||
levelInt, err := strconv.Atoi(level[0])
|
||||
if err != nil || levelInt < 0 {
|
||||
// if there is any issue with string to int conversion
|
||||
// we still default to MediumLevel of error correction
|
||||
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
|
||||
}
|
||||
|
||||
switch levelInt {
|
||||
case 1:
|
||||
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionLow)
|
||||
case 2:
|
||||
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
|
||||
case 3:
|
||||
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionQuart)
|
||||
case 4:
|
||||
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionHighest)
|
||||
default:
|
||||
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QRConfig) setSize() error {
|
||||
size, ok := q.Params["size"]
|
||||
|
||||
if ok {
|
||||
imageToBeResized, err := strconv.Atoi(size[0])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if imageToBeResized <= 0 {
|
||||
return errors.New("[qrops-error] Got an invalid size parameter, it should be greater than zero")
|
||||
}
|
||||
|
||||
q.Size = imageToBeResized
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QRConfig) setKeyUID() error {
|
||||
keyUID, ok := q.Params["keyUid"]
|
||||
// the keyUID was not passed, which is a requirement to get the multiaccount image,
|
||||
// so we log this error
|
||||
if !ok || len(keyUID) == 0 {
|
||||
return errors.New("[qrops-error] A keyUID is required to put logo on image and it was not passed in the parameters")
|
||||
}
|
||||
|
||||
q.KeyUID = keyUID[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QRConfig) setImageName() {
|
||||
imageName, ok := q.Params["imageName"]
|
||||
//if the imageName was not passed, we default to const images.LargeDimName
|
||||
if !ok || len(imageName) == 0 {
|
||||
q.ImageName = images.LargeDimName
|
||||
}
|
||||
|
||||
q.ImageName = imageName[0]
|
||||
}
|
||||
|
||||
func ToLogoImageFromBytes(imageBytes []byte, padding int) ([]byte, error) {
|
||||
img, _, err := image.Decode(bytes.NewReader(imageBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding image failed: %v", err)
|
||||
}
|
||||
paddedImg := images.AddPadding(img, padding)
|
||||
circle := images.CreateCircle(img)
|
||||
centeredImg := images.PlaceCircleInCenter(paddedImg, circle)
|
||||
resultBytes, err := images.EncodePNG(centeredImg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encoding PNG failed: %v", err)
|
||||
}
|
||||
return resultBytes, nil
|
||||
}
|
||||
|
||||
func GetLogoImage(multiaccountsDB *multiaccounts.Database, keyUID string, imageName string) ([]byte, error) {
|
||||
var (
|
||||
padding int
|
||||
LogoBytes []byte
|
||||
)
|
||||
|
||||
staticImageData, err := images.Asset("_assets/tests/qr/status.png")
|
||||
if err != nil { // Asset was not found.
|
||||
return nil, err
|
||||
}
|
||||
identityImageObjectFromDB, err := multiaccountsDB.GetIdentityImage(keyUID, imageName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if identityImageObjectFromDB == nil {
|
||||
padding = GetPadding(staticImageData)
|
||||
LogoBytes, err = ToLogoImageFromBytes(staticImageData, padding)
|
||||
} else {
|
||||
padding = GetPadding(identityImageObjectFromDB.Payload)
|
||||
LogoBytes, err = ToLogoImageFromBytes(identityImageObjectFromDB.Payload, padding)
|
||||
}
|
||||
|
||||
return LogoBytes, err
|
||||
}
|
||||
|
||||
func GetPadding(imgBytes []byte) int {
|
||||
const (
|
||||
defaultPadding = 20
|
||||
)
|
||||
size, _, err := images.GetImageDimensions(imgBytes)
|
||||
if err != nil {
|
||||
return defaultPadding
|
||||
}
|
||||
return size / 5
|
||||
}
|
||||
|
||||
func generateQRBytes(params url.Values, logger *zap.Logger, multiaccountsDB *multiaccounts.Database) []byte {
|
||||
|
||||
qrGenerationConfig, err := NewQRConfig(params, logger)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not generate QRConfig please rectify the errors with input parameters", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
qrc, err := qrcode.NewWith(qrGenerationConfig.DecodedQRURL,
|
||||
qrcode.WithEncodingMode(qrcode.EncModeAuto),
|
||||
qrGenerationConfig.CorrectionLevel,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not generate QRCode with provided options", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := NewWriterCloserByteBuffer()
|
||||
nw := standard.NewWithWriter(buf)
|
||||
err = qrc.Save(nw)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not save image", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
payload := buf.Bytes()
|
||||
|
||||
if qrGenerationConfig.WithLogo {
|
||||
logo, err := GetLogoImage(multiaccountsDB, qrGenerationConfig.KeyUID, qrGenerationConfig.ImageName)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not get logo image from multiaccountsDB", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
qrWidth, qrHeight, err := images.GetImageDimensions(payload)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not get image dimensions from payload", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
logo, err = images.ResizeImage(logo, qrWidth/5, qrHeight/5)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not resize logo image ", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
payload = images.SuperimposeLogoOnQRImage(payload, logo)
|
||||
}
|
||||
|
||||
if qrGenerationConfig.Size > 0 {
|
||||
|
||||
payload, err = images.ResizeImage(payload, qrGenerationConfig.Size, qrGenerationConfig.Size)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("could not resize final logo image ", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return payload
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
)
|
||||
|
||||
type QROpsTestSuite struct {
|
||||
suite.Suite
|
||||
TestKeyComponents
|
||||
TestLoggerComponents
|
||||
|
||||
server *MediaServer
|
||||
serverNoPort *MediaServer
|
||||
testStart time.Time
|
||||
multiaccountsDB *multiaccounts.Database
|
||||
}
|
||||
|
||||
var (
|
||||
keyUID = "0xdeadbeef"
|
||||
qrURL = "https://github.com/status-im/status-go/pull/3154"
|
||||
)
|
||||
|
||||
func TestQROpsTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(QROpsTestSuite))
|
||||
}
|
||||
|
||||
func (s *QROpsTestSuite) SetupTest() {
|
||||
s.SetupKeyComponents(s.T())
|
||||
s.SetupLoggerComponents()
|
||||
|
||||
mediaServer, err := NewMediaServer(nil, nil, nil)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.server = mediaServer
|
||||
err = s.server.SetPort(customPortForTests)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.server.Start()
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.serverNoPort = &MediaServer{Server: Server{
|
||||
hostname: DefaultIP.String(),
|
||||
portManger: newPortManager(s.Logger, nil),
|
||||
}}
|
||||
go func() {
|
||||
time.Sleep(waitTime)
|
||||
s.serverNoPort.port = 80
|
||||
}()
|
||||
|
||||
s.testStart = time.Now()
|
||||
}
|
||||
|
||||
func seedTestDBWithIdentityImagesForQRCodeTests(s *QROpsTestSuite, db *multiaccounts.Database, keyUID string) {
|
||||
iis := images.SampleIdentityImageForQRCode()
|
||||
err := db.StoreIdentityImages(keyUID, iis, false)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// TestQROpsCodeWithoutSuperImposingLogo tests the QR code generation logic, it also allows us to debug in case
|
||||
// things go wrong, here we compare generate a new QR code and compare its bytes with an already generated one.
|
||||
func (s *QROpsTestSuite) TestQROpsCodeWithoutSuperImposingLogo() {
|
||||
|
||||
var params = url.Values{}
|
||||
params.Set("url", base64.StdEncoding.EncodeToString([]byte(qrURL)))
|
||||
params.Set("allowProfileImage", "false")
|
||||
params.Set("level", "2")
|
||||
params.Set("size", "200")
|
||||
params.Set("imageName", "")
|
||||
|
||||
payload := generateQRBytes(params, s.Logger, s.multiaccountsDB)
|
||||
expectedPayload, err := images.Asset("_assets/tests/qr/defaultQR.png")
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotEmpty(payload)
|
||||
require.Equal(s.T(), payload, expectedPayload)
|
||||
}
|
||||
|
||||
func (s *QROpsTestSuite) TestQROpsCodeWithSuperImposingLogo() {
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "accounts-tests-")
|
||||
s.Require().NoError(err)
|
||||
|
||||
db, err := multiaccounts.InitializeDB(tmpfile.Name())
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedTestDBWithIdentityImagesForQRCodeTests(s, db, keyUID)
|
||||
|
||||
var params = url.Values{}
|
||||
params.Set("url", base64.StdEncoding.EncodeToString([]byte(qrURL)))
|
||||
params.Set("allowProfileImage", "true")
|
||||
params.Set("level", "2")
|
||||
params.Set("size", "200")
|
||||
params.Set("keyUid", keyUID)
|
||||
params.Set("imageName", "large")
|
||||
|
||||
payload := generateQRBytes(params, s.Logger, db)
|
||||
s.Require().NotEmpty(payload)
|
||||
expectedPayload, err := images.Asset("_assets/tests/qr/QRWithLogo.png")
|
||||
require.Equal(s.T(), payload, expectedPayload)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = db.Close()
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = os.Remove(tmpfile.Name())
|
||||
s.Require().NoError(err)
|
||||
}
|
|
@ -105,10 +105,20 @@ func (s *MediaServer) MakeStickerURL(stickerHash string) string {
|
|||
return u.String()
|
||||
}
|
||||
|
||||
func (s *MediaServer) MakeQRURL(qurul string) string {
|
||||
func (s *MediaServer) MakeQRURL(qurul string,
|
||||
allowProfileImage string,
|
||||
level string,
|
||||
size string,
|
||||
keyUID string,
|
||||
imageName string) string {
|
||||
u := s.MakeBaseURL()
|
||||
u.Path = generateQRCode
|
||||
u.RawQuery = url.Values{"qurul": {qurul}}.Encode()
|
||||
u.RawQuery = url.Values{"url": {qurul},
|
||||
"level": {level},
|
||||
"allowProfileImage": {allowProfileImage},
|
||||
"size": {size},
|
||||
"keyUid": {keyUID},
|
||||
"imageName": {imageName}}.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,33 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/status-im/status-go/images"
|
||||
)
|
||||
|
||||
const (
|
||||
waitTime = 50 * time.Millisecond
|
||||
waitTime = 50 * time.Millisecond
|
||||
customPortForTests = 1337
|
||||
defaultPortForTests = 80
|
||||
)
|
||||
|
||||
var (
|
||||
baseURL = "https://127.0.0.1"
|
||||
baseURLWithCustomPort = fmt.Sprintf("%s:%d", baseURL, customPortForTests)
|
||||
baseURLWithDefaultPort = fmt.Sprintf("%s:%d", baseURL, defaultPortForTests)
|
||||
)
|
||||
|
||||
func TestServerURLSuite(t *testing.T) {
|
||||
|
@ -21,6 +40,7 @@ type ServerURLSuite struct {
|
|||
TestLoggerComponents
|
||||
|
||||
server *MediaServer
|
||||
serverForQR *MediaServer
|
||||
serverNoPort *MediaServer
|
||||
testStart time.Time
|
||||
}
|
||||
|
@ -29,11 +49,19 @@ func (s *ServerURLSuite) SetupTest() {
|
|||
s.SetupKeyComponents(s.T())
|
||||
s.SetupLoggerComponents()
|
||||
|
||||
mediaServer, err := NewMediaServer(nil, nil, nil)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.serverForQR = mediaServer
|
||||
|
||||
err = s.serverForQR.Start()
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.server = &MediaServer{Server: Server{
|
||||
hostname: DefaultIP.String(),
|
||||
portManger: newPortManager(s.Logger, nil),
|
||||
}}
|
||||
err := s.server.SetPort(1337)
|
||||
err = s.server.SetPort(customPortForTests)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.serverNoPort = &MediaServer{Server: Server{
|
||||
|
@ -42,7 +70,7 @@ func (s *ServerURLSuite) SetupTest() {
|
|||
}}
|
||||
go func() {
|
||||
time.Sleep(waitTime)
|
||||
s.serverNoPort.port = 80
|
||||
s.serverNoPort.port = defaultPortForTests
|
||||
}()
|
||||
|
||||
s.testStart = time.Now()
|
||||
|
@ -58,48 +86,130 @@ func (s *ServerURLSuite) testNoPort(expected string, actual string) {
|
|||
}
|
||||
|
||||
func (s *ServerURLSuite) TestServer_MakeBaseURL() {
|
||||
s.Require().Equal("https://127.0.0.1:1337", s.server.MakeBaseURL().String())
|
||||
s.testNoPort("https://127.0.0.1:80", s.serverNoPort.MakeBaseURL().String())
|
||||
s.Require().Equal(baseURLWithCustomPort, s.server.MakeBaseURL().String())
|
||||
s.testNoPort(baseURLWithDefaultPort, s.serverNoPort.MakeBaseURL().String())
|
||||
}
|
||||
|
||||
func (s *ServerURLSuite) TestServer_MakeImageServerURL() {
|
||||
s.Require().Equal("https://127.0.0.1:1337/messages/", s.server.MakeImageServerURL())
|
||||
s.testNoPort("https://127.0.0.1:80/messages/", s.serverNoPort.MakeImageServerURL())
|
||||
s.Require().Equal(baseURLWithCustomPort+"/messages/", s.server.MakeImageServerURL())
|
||||
s.testNoPort(baseURLWithDefaultPort+"/messages/", s.serverNoPort.MakeImageServerURL())
|
||||
}
|
||||
|
||||
func (s *ServerURLSuite) TestServer_MakeIdenticonURL() {
|
||||
s.Require().Equal(
|
||||
"https://127.0.0.1:1337/messages/identicons?publicKey=0xdaff0d11decade",
|
||||
baseURLWithCustomPort+"/messages/identicons?publicKey=0xdaff0d11decade",
|
||||
s.server.MakeIdenticonURL("0xdaff0d11decade"))
|
||||
s.testNoPort(
|
||||
"https://127.0.0.1:80/messages/identicons?publicKey=0xdaff0d11decade",
|
||||
baseURLWithDefaultPort+"/messages/identicons?publicKey=0xdaff0d11decade",
|
||||
s.serverNoPort.MakeIdenticonURL("0xdaff0d11decade"))
|
||||
}
|
||||
|
||||
func (s *ServerURLSuite) TestServer_MakeImageURL() {
|
||||
s.Require().Equal(
|
||||
"https://127.0.0.1:1337/messages/images?messageId=0x10aded70ffee",
|
||||
baseURLWithCustomPort+"/messages/images?messageId=0x10aded70ffee",
|
||||
s.server.MakeImageURL("0x10aded70ffee"))
|
||||
|
||||
s.testNoPort(
|
||||
"https://127.0.0.1:80/messages/images?messageId=0x10aded70ffee",
|
||||
baseURLWithDefaultPort+"/messages/images?messageId=0x10aded70ffee",
|
||||
s.serverNoPort.MakeImageURL("0x10aded70ffee"))
|
||||
}
|
||||
|
||||
func (s *ServerURLSuite) TestServer_MakeAudioURL() {
|
||||
s.Require().Equal(
|
||||
"https://127.0.0.1:1337/messages/audio?messageId=0xde1e7ebee71e",
|
||||
baseURLWithCustomPort+"/messages/audio?messageId=0xde1e7ebee71e",
|
||||
s.server.MakeAudioURL("0xde1e7ebee71e"))
|
||||
s.testNoPort(
|
||||
"https://127.0.0.1:80/messages/audio?messageId=0xde1e7ebee71e",
|
||||
baseURLWithDefaultPort+"/messages/audio?messageId=0xde1e7ebee71e",
|
||||
s.serverNoPort.MakeAudioURL("0xde1e7ebee71e"))
|
||||
}
|
||||
|
||||
func (s *ServerURLSuite) TestServer_MakeStickerURL() {
|
||||
s.Require().Equal(
|
||||
"https://127.0.0.1:1337/ipfs?hash=0xdeadbeef4ac0",
|
||||
baseURLWithCustomPort+"/ipfs?hash=0xdeadbeef4ac0",
|
||||
s.server.MakeStickerURL("0xdeadbeef4ac0"))
|
||||
s.testNoPort(
|
||||
"https://127.0.0.1:80/ipfs?hash=0xdeadbeef4ac0",
|
||||
baseURLWithDefaultPort+"/ipfs?hash=0xdeadbeef4ac0",
|
||||
s.serverNoPort.MakeStickerURL("0xdeadbeef4ac0"))
|
||||
}
|
||||
|
||||
// TestQRCodeGeneration tests if we provide all the correct parameters to the media server
|
||||
// do we get a valid QR code or not as part of the response payload.
|
||||
// we have stored a generated QR code in tests folder, and we compare their bytes.
|
||||
func (s *ServerURLSuite) TestQRCodeGeneration() {
|
||||
|
||||
qrURL := "https://github.com/status-im/status-go/pull/3154"
|
||||
generatedURL := base64.StdEncoding.EncodeToString([]byte(qrURL))
|
||||
generatedURL = s.serverForQR.MakeQRURL(generatedURL, "false", "2", "200", "", "")
|
||||
|
||||
u, err := url.Parse(generatedURL)
|
||||
if err != nil {
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
if u.Scheme == "" || u.Host == "" {
|
||||
s.Require().Failf("generatedURL is not a valid URL: %s", generatedURL)
|
||||
}
|
||||
|
||||
serverCert := s.serverForQR.cert
|
||||
serverCertBytes := serverCert.Certificate[0]
|
||||
|
||||
certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCertBytes})
|
||||
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
_ = rootCAs.AppendCertsFromPEM(certPem)
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: true, //nolint:all // MUST BE FALSE
|
||||
RootCAs: rootCAs,
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, generatedURL, nil)
|
||||
if err != nil {
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
s.Require().Failf("Unexpected response status code: %d", fmt.Sprint(resp.StatusCode))
|
||||
}
|
||||
|
||||
payload, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
s.Require().NotEmpty(payload)
|
||||
|
||||
expectedPayload, err := images.Asset("_assets/tests/qr/defaultQR.png")
|
||||
require.Equal(s.T(), payload, expectedPayload)
|
||||
s.Require().NoError(err)
|
||||
|
||||
//(siddarthkay) un-comment code block below to generate the file in tests folder
|
||||
//f, err := os.Create("image.png")
|
||||
//if err != nil {
|
||||
// s.Require().NoError(err)
|
||||
//
|
||||
//}
|
||||
//defer f.Close()
|
||||
//_, err = f.Write(payload)
|
||||
//
|
||||
//if err != nil {
|
||||
// s.Require().NoError(err)
|
||||
//}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue