mirror of
https://github.com/status-im/status-go.git
synced 2025-01-09 06:12:55 +00:00
2cac37c1fb
We should emit an error when the request to an image to be fetched returns and HTTP error code. Otherwise, we'll run into other higher level errors down the line, which are misleading Example: I kept seeing "image content type not supported" errors, although the content type *is* supported. The actual problem was that the decode function operates on non existing image bytes.
155 lines
2.7 KiB
Go
155 lines
2.7 KiB
Go
package images
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"image"
|
|
"image/gif"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"golang.org/x/image/webp"
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
)
|
|
|
|
func Decode(fileName string) (image.Image, error) {
|
|
file, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
fb, err := prepareFileForDecode(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return decodeImageData(fb, file)
|
|
}
|
|
|
|
func DecodeFromURL(path string) (image.Image, error) {
|
|
client := http.Client{
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
res, err := client.Get(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if err := res.Body.Close(); err != nil {
|
|
log.Error("failed to close profile pic http request body", "err", err)
|
|
}
|
|
}()
|
|
|
|
if res.StatusCode >= 400 {
|
|
return nil, errors.New(http.StatusText(res.StatusCode))
|
|
}
|
|
|
|
bodyBytes, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return decodeImageData(bodyBytes, bytes.NewReader(bodyBytes))
|
|
}
|
|
|
|
func prepareFileForDecode(file *os.File) ([]byte, error) {
|
|
// Read the first 14 bytes, used for performing image type checks before parsing the image data
|
|
fb := make([]byte, 14)
|
|
_, err := file.Read(fb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Reset the read cursor
|
|
_, err = file.Seek(0, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fb, nil
|
|
}
|
|
|
|
func decodeImageData(buf []byte, r io.Reader) (img image.Image, err error) {
|
|
switch GetType(buf) {
|
|
case JPEG:
|
|
img, err = jpeg.Decode(r)
|
|
case PNG:
|
|
img, err = png.Decode(r)
|
|
case GIF:
|
|
img, err = gif.Decode(r)
|
|
case WEBP:
|
|
img, err = webp.Decode(r)
|
|
case UNKNOWN:
|
|
fallthrough
|
|
default:
|
|
return nil, errors.New("unsupported file type")
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return img, nil
|
|
}
|
|
|
|
func GetType(buf []byte) ImageType {
|
|
switch {
|
|
case isJpeg(buf):
|
|
return JPEG
|
|
case isPng(buf):
|
|
return PNG
|
|
case isGif(buf):
|
|
return GIF
|
|
case isWebp(buf):
|
|
return WEBP
|
|
default:
|
|
return UNKNOWN
|
|
}
|
|
}
|
|
|
|
func GetMimeType(buf []byte) (string, error) {
|
|
switch {
|
|
case isJpeg(buf):
|
|
return "jpeg", nil
|
|
case isPng(buf):
|
|
return "png", nil
|
|
case isGif(buf):
|
|
return "gif", nil
|
|
case isWebp(buf):
|
|
return "webp", nil
|
|
default:
|
|
return "", errors.New("image format not supported")
|
|
}
|
|
}
|
|
|
|
func isJpeg(buf []byte) bool {
|
|
return len(buf) > 2 &&
|
|
buf[0] == 0xFF &&
|
|
buf[1] == 0xD8 &&
|
|
buf[2] == 0xFF
|
|
}
|
|
|
|
func isPng(buf []byte) bool {
|
|
return len(buf) > 3 &&
|
|
buf[0] == 0x89 && buf[1] == 0x50 &&
|
|
buf[2] == 0x4E && buf[3] == 0x47
|
|
}
|
|
|
|
func isGif(buf []byte) bool {
|
|
return len(buf) > 2 &&
|
|
buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46
|
|
}
|
|
|
|
func isWebp(buf []byte) bool {
|
|
return len(buf) > 11 &&
|
|
buf[8] == 0x57 && buf[9] == 0x45 &&
|
|
buf[10] == 0x42 && buf[11] == 0x50
|
|
}
|