mirror of https://github.com/jlelse/GoBlog
Refactor media compression
This commit is contained in:
parent
10ab12764a
commit
f96a06beac
3
app.go
3
app.go
|
@ -75,6 +75,9 @@ type goBlog struct {
|
||||||
logf *rotatelogs.RotateLogs
|
logf *rotatelogs.RotateLogs
|
||||||
// Markdown
|
// Markdown
|
||||||
md, absoluteMd goldmark.Markdown
|
md, absoluteMd goldmark.Markdown
|
||||||
|
// Media
|
||||||
|
compressorsInit sync.Once
|
||||||
|
compressors []mediaCompression
|
||||||
// Minify
|
// Minify
|
||||||
min minify.Minifier
|
min minify.Minifier
|
||||||
// Regex Redirects
|
// Regex Redirects
|
||||||
|
|
|
@ -1,35 +1,43 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"net/http/httptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeHttpClient struct {
|
type fakeHttpClient struct {
|
||||||
req *http.Request
|
req *http.Request
|
||||||
res *http.Response
|
res *http.Response
|
||||||
err error
|
handler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeHttpClient) Do(req *http.Request) (*http.Response, error) {
|
func (c *fakeHttpClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
if c.handler == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c.handler.ServeHTTP(rec, req)
|
||||||
c.req = req
|
c.req = req
|
||||||
return c.res, c.err
|
c.res = rec.Result()
|
||||||
|
return c.res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeHttpClient) clean() {
|
func (c *fakeHttpClient) clean() {
|
||||||
c.req = nil
|
c.req = nil
|
||||||
c.err = nil
|
|
||||||
c.res = nil
|
c.res = nil
|
||||||
|
c.handler = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeHttpClient) setFakeResponse(statusCode int, body string, err error) {
|
func (c *fakeHttpClient) setHandler(handler http.Handler) {
|
||||||
c.clean()
|
c.clean()
|
||||||
c.err = err
|
c.handler = handler
|
||||||
c.res = &http.Response{
|
}
|
||||||
StatusCode: statusCode,
|
|
||||||
Body: io.NopCloser(strings.NewReader(body)),
|
func (c *fakeHttpClient) setFakeResponse(statusCode int, body string) {
|
||||||
}
|
c.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
rw.WriteHeader(statusCode)
|
||||||
|
rw.Write([]byte(body))
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFakeHTTPClient() *fakeHttpClient {
|
func getFakeHTTPClient() *fakeHttpClient {
|
||||||
|
|
|
@ -15,99 +15,60 @@ import (
|
||||||
const defaultCompressionWidth = 2000
|
const defaultCompressionWidth = 2000
|
||||||
const defaultCompressionHeight = 3000
|
const defaultCompressionHeight = 3000
|
||||||
|
|
||||||
func (a *goBlog) tinify(url string, config *configMicropubMedia) (location string, err error) {
|
type mediaCompression interface {
|
||||||
// Check config
|
compress(url string, save fileUploadFunc, hc httpClient) (location string, err error)
|
||||||
if config == nil || config.TinifyKey == "" {
|
|
||||||
return "", errors.New("service Tinify not configured")
|
|
||||||
}
|
|
||||||
// Check url
|
|
||||||
fileExtension, allowed := compressionIsSupported(url, "jpg", "jpeg", "png")
|
|
||||||
if !allowed {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
// Compress
|
|
||||||
j, _ := json.Marshal(map[string]interface{}{
|
|
||||||
"source": map[string]interface{}{
|
|
||||||
"url": url,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
req, err := http.NewRequest(http.MethodPost, "https://api.tinify.com/shrink", bytes.NewReader(j))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
req.SetBasicAuth("api", config.TinifyKey)
|
|
||||||
req.Header.Set(contentType, contenttype.JSON)
|
|
||||||
resp, err := a.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
|
||||||
return "", fmt.Errorf("failed to compress image, status code %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
compressedLocation := resp.Header.Get("Location")
|
|
||||||
if compressedLocation == "" {
|
|
||||||
return "", errors.New("tinify didn't return compressed location")
|
|
||||||
}
|
|
||||||
// Resize and download image
|
|
||||||
j, _ = json.Marshal(map[string]interface{}{
|
|
||||||
"resize": map[string]interface{}{
|
|
||||||
"method": "fit",
|
|
||||||
"width": defaultCompressionWidth,
|
|
||||||
"height": defaultCompressionHeight,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
downloadReq, err := http.NewRequest(http.MethodPost, compressedLocation, bytes.NewReader(j))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
downloadReq.SetBasicAuth("api", config.TinifyKey)
|
|
||||||
downloadReq.Header.Set(contentType, contenttype.JSON)
|
|
||||||
downloadResp, err := a.httpClient.Do(downloadReq)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer downloadResp.Body.Close()
|
|
||||||
if downloadResp.StatusCode != http.StatusOK {
|
|
||||||
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
|
||||||
return "", fmt.Errorf("tinify failed to resize image, status code %d", downloadResp.StatusCode)
|
|
||||||
}
|
|
||||||
tmpFile, err := os.CreateTemp("", "tiny-*."+fileExtension)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tmpFile.Close()
|
|
||||||
_ = os.Remove(tmpFile.Name())
|
|
||||||
}()
|
|
||||||
if _, err = io.Copy(tmpFile, downloadResp.Body); err != nil {
|
|
||||||
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
fileName, err := getSHA256(tmpFile)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// Upload compressed file
|
|
||||||
location, err = a.uploadFile(fileName+"."+fileExtension, tmpFile)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) shortPixel(url string, config *configMicropubMedia) (location string, err error) {
|
type shortpixel struct {
|
||||||
// Check config
|
key string
|
||||||
if config == nil || config.ShortPixelKey == "" {
|
}
|
||||||
return "", errors.New("service ShortPixel not configured")
|
|
||||||
|
type tinify struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
type cloudflare struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) compressMediaFile(url string) (location string, err error) {
|
||||||
|
// Init compressors
|
||||||
|
a.compressorsInit.Do(a.initMediaCompressors)
|
||||||
|
// Try all compressors until success
|
||||||
|
for _, c := range a.compressors {
|
||||||
|
location, err = c.compress(url, a.uploadFile, a.httpClient)
|
||||||
|
if location != "" && err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Return result
|
||||||
|
return location, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) initMediaCompressors() {
|
||||||
|
config := a.cfg.Micropub.MediaStorage
|
||||||
|
if config == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if key := config.ShortPixelKey; key != "" {
|
||||||
|
a.compressors = append(a.compressors, &shortpixel{key})
|
||||||
|
}
|
||||||
|
if key := config.TinifyKey; key != "" {
|
||||||
|
a.compressors = append(a.compressors, &tinify{key})
|
||||||
|
}
|
||||||
|
if config.CloudflareCompressionEnabled {
|
||||||
|
a.compressors = append(a.compressors, &cloudflare{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *shortpixel) compress(url string, upload fileUploadFunc, hc httpClient) (location string, err error) {
|
||||||
// Check url
|
// Check url
|
||||||
fileExtension, allowed := compressionIsSupported(url, "jpg", "jpeg", "png")
|
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
// Compress
|
// Compress
|
||||||
j, _ := json.Marshal(map[string]interface{}{
|
j, _ := json.Marshal(map[string]interface{}{
|
||||||
"key": config.ShortPixelKey,
|
"key": sp.key,
|
||||||
"plugin_version": "GB001",
|
"plugin_version": "GB001",
|
||||||
"lossy": 1,
|
"lossy": 1,
|
||||||
"resize": 3,
|
"resize": 3,
|
||||||
|
@ -121,7 +82,7 @@ func (a *goBlog) shortPixel(url string, config *configMicropubMedia) (location s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
resp, err := a.httpClient.Do(req)
|
resp, err := hc.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -148,13 +109,89 @@ func (a *goBlog) shortPixel(url string, config *configMicropubMedia) (location s
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// Upload compressed file
|
// Upload compressed file
|
||||||
location, err = a.uploadFile(fileName+"."+fileExtension, tmpFile)
|
location, err = upload(fileName+"."+fileExtension, tmpFile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) cloudflare(url string) (location string, err error) {
|
func (tf *tinify) compress(url string, upload fileUploadFunc, hc httpClient) (location string, err error) {
|
||||||
// Check url
|
// Check url
|
||||||
_, allowed := compressionIsSupported(url, "jpg", "jpeg", "png")
|
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
||||||
|
if !allowed {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
// Compress
|
||||||
|
j, _ := json.Marshal(map[string]interface{}{
|
||||||
|
"source": map[string]interface{}{
|
||||||
|
"url": url,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "https://api.tinify.com/shrink", bytes.NewReader(j))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.SetBasicAuth("api", tf.key)
|
||||||
|
req.Header.Set(contentType, contenttype.JSON)
|
||||||
|
resp, err := hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
_, _ = io.Copy(io.Discard, resp.Body)
|
||||||
|
if resp.StatusCode != http.StatusCreated {
|
||||||
|
return "", fmt.Errorf("failed to compress image, status code %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
compressedLocation := resp.Header.Get("Location")
|
||||||
|
if compressedLocation == "" {
|
||||||
|
return "", errors.New("tinify didn't return compressed location")
|
||||||
|
}
|
||||||
|
// Resize and download image
|
||||||
|
j, _ = json.Marshal(map[string]interface{}{
|
||||||
|
"resize": map[string]interface{}{
|
||||||
|
"method": "fit",
|
||||||
|
"width": defaultCompressionWidth,
|
||||||
|
"height": defaultCompressionHeight,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
downloadReq, err := http.NewRequest(http.MethodPost, compressedLocation, bytes.NewReader(j))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
downloadReq.SetBasicAuth("api", tf.key)
|
||||||
|
downloadReq.Header.Set(contentType, contenttype.JSON)
|
||||||
|
downloadResp, err := hc.Do(downloadReq)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer downloadResp.Body.Close()
|
||||||
|
if downloadResp.StatusCode != http.StatusOK {
|
||||||
|
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
||||||
|
return "", fmt.Errorf("tinify failed to resize image, status code %d", downloadResp.StatusCode)
|
||||||
|
}
|
||||||
|
tmpFile, err := os.CreateTemp("", "tiny-*."+fileExtension)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tmpFile.Close()
|
||||||
|
_ = os.Remove(tmpFile.Name())
|
||||||
|
}()
|
||||||
|
if _, err = io.Copy(tmpFile, downloadResp.Body); err != nil {
|
||||||
|
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fileName, err := getSHA256(tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Upload compressed file
|
||||||
|
location, err = upload(fileName+"."+fileExtension, tmpFile)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cf *cloudflare) compress(url string, upload fileUploadFunc, hc httpClient) (location string, err error) {
|
||||||
|
// Check url
|
||||||
|
_, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -165,7 +202,7 @@ func (a *goBlog) cloudflare(url string) (location string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
resp, err := a.httpClient.Do(req)
|
resp, err := hc.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -192,6 +229,6 @@ func (a *goBlog) cloudflare(url string) (location string, err error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// Upload compressed file
|
// Upload compressed file
|
||||||
location, err = a.uploadFile(fileName+"."+fileExtension, tmpFile)
|
location, err = upload(fileName+"."+fileExtension, tmpFile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_compress(t *testing.T) {
|
||||||
|
fakeFileContent := "Test"
|
||||||
|
fakeFileName := filepath.Join(t.TempDir(), "test.jpg")
|
||||||
|
err := os.WriteFile(fakeFileName, []byte(fakeFileContent), 0777)
|
||||||
|
require.Nil(t, err)
|
||||||
|
fakeFile, err := os.Open(fakeFileName)
|
||||||
|
require.Nil(t, err)
|
||||||
|
fakeSha256, err := getSHA256(fakeFile)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
var uf fileUploadFunc = func(filename string, f io.Reader) (location string, err error) {
|
||||||
|
return "https://example.com/" + filename, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Cloudflare", func(t *testing.T) {
|
||||||
|
fakeClient := getFakeHTTPClient()
|
||||||
|
fakeClient.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, "https://www.cloudflare.com/cdn-cgi/image/f=jpeg,q=75,metadata=none,fit=scale-down,w=2000,h=3000/https://example.com/original.jpg", r.URL.String())
|
||||||
|
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
rw.Write([]byte(fakeFileContent))
|
||||||
|
}))
|
||||||
|
|
||||||
|
cf := &cloudflare{}
|
||||||
|
res, err := cf.compress("https://example.com/original.jpg", uf, fakeClient)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "https://example.com/"+fakeSha256+".jpeg", res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Shortpixel", func(t *testing.T) {
|
||||||
|
fakeClient := getFakeHTTPClient()
|
||||||
|
fakeClient.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.Equal(t, "https://api.shortpixel.com/v2/reducer-sync.php", r.URL.String())
|
||||||
|
|
||||||
|
requestBody, _ := io.ReadAll(r.Body)
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
var requestJson map[string]interface{}
|
||||||
|
err = json.Unmarshal(requestBody, &requestJson)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, requestJson)
|
||||||
|
|
||||||
|
assert.Equal(t, "testkey", requestJson["key"])
|
||||||
|
assert.Equal(t, "https://example.com/original.jpg", requestJson["url"])
|
||||||
|
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
rw.Write([]byte(fakeFileContent))
|
||||||
|
}))
|
||||||
|
|
||||||
|
cf := &shortpixel{"testkey"}
|
||||||
|
res, err := cf.compress("https://example.com/original.jpg", uf, fakeClient)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "https://example.com/"+fakeSha256+".jpg", res)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -9,7 +8,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
|
"git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
|
||||||
|
@ -63,46 +61,22 @@ func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Try to compress file (only when not in private mode)
|
// Try to compress file (only when not in private mode)
|
||||||
if pm := a.cfg.PrivateMode; !(pm != nil && pm.Enabled) {
|
if pm := a.cfg.PrivateMode; pm == nil || !pm.Enabled {
|
||||||
serveCompressionError := func(ce error) {
|
compressedLocation, compressionErr := a.compressMediaFile(location)
|
||||||
a.serveError(w, r, "failed to compress file: "+ce.Error(), http.StatusInternalServerError)
|
if compressionErr != nil {
|
||||||
|
a.serveError(w, r, "failed to compress file: "+compressionErr.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
var compressedLocation string
|
// Overwrite location
|
||||||
var compressionErr error
|
if compressedLocation != "" {
|
||||||
if ms := a.cfg.Micropub.MediaStorage; ms != nil {
|
location = compressedLocation
|
||||||
// Default ShortPixel
|
|
||||||
if ms.ShortPixelKey != "" {
|
|
||||||
compressedLocation, compressionErr = a.shortPixel(location, ms)
|
|
||||||
}
|
|
||||||
if compressionErr != nil {
|
|
||||||
serveCompressionError(compressionErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Fallback Tinify
|
|
||||||
if compressedLocation == "" && ms.TinifyKey != "" {
|
|
||||||
compressedLocation, compressionErr = a.tinify(location, ms)
|
|
||||||
}
|
|
||||||
if compressionErr != nil {
|
|
||||||
serveCompressionError(compressionErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Fallback Cloudflare
|
|
||||||
if compressedLocation == "" && ms.CloudflareCompressionEnabled {
|
|
||||||
compressedLocation, compressionErr = a.cloudflare(location)
|
|
||||||
}
|
|
||||||
if compressionErr != nil {
|
|
||||||
serveCompressionError(compressionErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Overwrite location
|
|
||||||
if compressedLocation != "" {
|
|
||||||
location = compressedLocation
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, location, http.StatusCreated)
|
http.Redirect(w, r, location, http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fileUploadFunc func(filename string, f io.Reader) (location string, err error)
|
||||||
|
|
||||||
func (a *goBlog) uploadFile(filename string, f io.Reader) (string, error) {
|
func (a *goBlog) uploadFile(filename string, f io.Reader) (string, error) {
|
||||||
ms := a.cfg.Micropub.MediaStorage
|
ms := a.cfg.Micropub.MediaStorage
|
||||||
if ms != nil && ms.BunnyStorageKey != "" && ms.BunnyStorageName != "" {
|
if ms != nil && ms.BunnyStorageKey != "" && ms.BunnyStorageName != "" {
|
||||||
|
@ -136,27 +110,3 @@ func (a *goBlog) uploadToBunny(filename string, f io.Reader) (location string, e
|
||||||
}
|
}
|
||||||
return config.MediaURL + "/" + filename, nil
|
return config.MediaURL + "/" + filename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compressionIsSupported(url string, allowed ...string) (string, bool) {
|
|
||||||
spliced := strings.Split(url, ".")
|
|
||||||
ext := spliced[len(spliced)-1]
|
|
||||||
sort.Strings(allowed)
|
|
||||||
if i := sort.SearchStrings(allowed, strings.ToLower(ext)); i >= len(allowed) || allowed[i] != strings.ToLower(ext) {
|
|
||||||
return ext, false
|
|
||||||
}
|
|
||||||
return ext, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSHA256(file io.ReadSeeker) (filename string, err error) {
|
|
||||||
if _, err = file.Seek(0, 0); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
h := sha256.New()
|
|
||||||
if _, err = io.Copy(h, file); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, err = file.Seek(0, 0); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -83,13 +83,12 @@ func Test_configTelegram_send(t *testing.T) {
|
||||||
httpClient: fakeClient,
|
httpClient: fakeClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeClient.setFakeResponse(200, "", nil)
|
fakeClient.setFakeResponse(200, "")
|
||||||
|
|
||||||
err := app.send(tg, "Message", "HTML")
|
err := app.send(tg, "Message", "HTML")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
assert.NotNil(t, fakeClient.req)
|
assert.NotNil(t, fakeClient.req)
|
||||||
assert.Nil(t, fakeClient.err)
|
|
||||||
assert.Equal(t, http.MethodPost, fakeClient.req.Method)
|
assert.Equal(t, http.MethodPost, fakeClient.req.Method)
|
||||||
assert.Equal(t, "https://api.telegram.org/botbottoken/sendMessage?chat_id=chatid&parse_mode=HTML&text=Message", fakeClient.req.URL.String())
|
assert.Equal(t, "https://api.telegram.org/botbottoken/sendMessage?chat_id=chatid&parse_mode=HTML&text=Message", fakeClient.req.URL.String())
|
||||||
}
|
}
|
||||||
|
@ -110,7 +109,7 @@ func Test_telegram(t *testing.T) {
|
||||||
t.Run("Send post to Telegram", func(t *testing.T) {
|
t.Run("Send post to Telegram", func(t *testing.T) {
|
||||||
fakeClient := getFakeHTTPClient()
|
fakeClient := getFakeHTTPClient()
|
||||||
|
|
||||||
fakeClient.setFakeResponse(200, "", nil)
|
fakeClient.setFakeResponse(200, "")
|
||||||
|
|
||||||
app := &goBlog{
|
app := &goBlog{
|
||||||
pPostHooks: []postHookFunc{},
|
pPostHooks: []postHookFunc{},
|
||||||
|
@ -157,7 +156,7 @@ func Test_telegram(t *testing.T) {
|
||||||
t.Run("Telegram disabled", func(t *testing.T) {
|
t.Run("Telegram disabled", func(t *testing.T) {
|
||||||
fakeClient := getFakeHTTPClient()
|
fakeClient := getFakeHTTPClient()
|
||||||
|
|
||||||
fakeClient.setFakeResponse(200, "", nil)
|
fakeClient.setFakeResponse(200, "")
|
||||||
|
|
||||||
app := &goBlog{
|
app := &goBlog{
|
||||||
pPostHooks: []postHookFunc{},
|
pPostHooks: []postHookFunc{},
|
||||||
|
|
35
utils.go
35
utils.go
|
@ -1,10 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -191,3 +194,35 @@ func charCount(s string) (count int) {
|
||||||
func wrapStringAsHTML(s string) template.HTML {
|
func wrapStringAsHTML(s string) template.HTML {
|
||||||
return template.HTML(s)
|
return template.HTML(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if url has allowed file extension
|
||||||
|
func urlHasExt(rawUrl string, allowed ...string) (ext string, has bool) {
|
||||||
|
u, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
ext = strings.ToLower(path.Ext(u.Path))
|
||||||
|
if ext == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
ext = ext[1:]
|
||||||
|
allowed = funk.Map(allowed, func(str string) string {
|
||||||
|
return strings.ToLower(str)
|
||||||
|
}).([]string)
|
||||||
|
return ext, funk.ContainsString(allowed, strings.ToLower(ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get SHA-256 hash of file
|
||||||
|
func getSHA256(file io.ReadSeeker) (filename string, err error) {
|
||||||
|
if _, err = file.Seek(0, 0); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
h := sha256.New()
|
||||||
|
if _, err = io.Copy(h, file); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err = file.Seek(0, 0); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
|
|
@ -83,3 +83,16 @@ func Test_allLinksFromHTMLString(t *testing.T) {
|
||||||
t.Errorf("Wrong result, got: %v", result)
|
t.Errorf("Wrong result, got: %v", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_urlHasExt(t *testing.T) {
|
||||||
|
t.Run("Simple", func(t *testing.T) {
|
||||||
|
ext, res := urlHasExt("https://example.com/test.jpg", "png", "jpg", "webp")
|
||||||
|
assert.True(t, res)
|
||||||
|
assert.Equal(t, "jpg", ext)
|
||||||
|
})
|
||||||
|
t.Run("Strange case", func(t *testing.T) {
|
||||||
|
ext, res := urlHasExt("https://example.com/test.jpG", "PnG", "JPg", "WEBP")
|
||||||
|
assert.True(t, res)
|
||||||
|
assert.Equal(t, "jpg", ext)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue