github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/imageproxy/imageproxy.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package imageproxy
     5  
     6  import (
     7  	"errors"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/masterhung0112/hk_server/v5/model"
    15  	"github.com/masterhung0112/hk_server/v5/services/configservice"
    16  	"github.com/masterhung0112/hk_server/v5/services/httpservice"
    17  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    18  )
    19  
    20  var ErrNotEnabled = Error{errors.New("imageproxy.ImageProxy: image proxy not enabled")}
    21  
    22  // An ImageProxy is the public interface for Mattermost's image proxy. An instance of ImageProxy should be created
    23  // using MakeImageProxy which requires a configService and an HTTPService provided by the server.
    24  type ImageProxy struct {
    25  	ConfigService    configservice.ConfigService
    26  	configListenerID string
    27  
    28  	HTTPService httpservice.HTTPService
    29  
    30  	Logger *mlog.Logger
    31  
    32  	siteURL *url.URL
    33  	lock    sync.RWMutex
    34  	backend ImageProxyBackend
    35  }
    36  
    37  // An ImageProxyBackend provides the functionality for different types of image proxies. An ImageProxy will construct
    38  // the required backend depending on the ImageProxySettings provided by the ConfigService.
    39  type ImageProxyBackend interface {
    40  	// GetImage provides a proxied image in response to an HTTP request.
    41  	GetImage(w http.ResponseWriter, r *http.Request, imageURL string)
    42  
    43  	// GetImageDirect returns a proxied image along with its content type.
    44  	GetImageDirect(imageURL string) (io.ReadCloser, string, error)
    45  }
    46  
    47  func MakeImageProxy(configService configservice.ConfigService, httpService httpservice.HTTPService, logger *mlog.Logger) *ImageProxy {
    48  	proxy := &ImageProxy{
    49  		ConfigService: configService,
    50  		HTTPService:   httpService,
    51  		Logger:        logger,
    52  	}
    53  
    54  	// We deliberately ignore the error because it's from config.json.
    55  	// The function returns a nil pointer in case of error, and we handle it when it's used.
    56  	siteURL, _ := url.Parse(*configService.Config().ServiceSettings.SiteURL)
    57  	proxy.siteURL = siteURL
    58  
    59  	proxy.configListenerID = proxy.ConfigService.AddConfigListener(proxy.OnConfigChange)
    60  
    61  	config := proxy.ConfigService.Config()
    62  	proxy.backend = proxy.makeBackend(*config.ImageProxySettings.Enable, *config.ImageProxySettings.ImageProxyType)
    63  
    64  	return proxy
    65  }
    66  
    67  func (proxy *ImageProxy) makeBackend(enable bool, proxyType string) ImageProxyBackend {
    68  	if !enable {
    69  		return nil
    70  	}
    71  
    72  	switch proxyType {
    73  	case model.IMAGE_PROXY_TYPE_LOCAL:
    74  		return makeLocalBackend(proxy)
    75  	case model.IMAGE_PROXY_TYPE_ATMOS_CAMO:
    76  		return makeAtmosCamoBackend(proxy)
    77  	default:
    78  		return nil
    79  	}
    80  }
    81  
    82  func (proxy *ImageProxy) Close() {
    83  	proxy.lock.Lock()
    84  	defer proxy.lock.Unlock()
    85  
    86  	proxy.ConfigService.RemoveConfigListener(proxy.configListenerID)
    87  }
    88  
    89  func (proxy *ImageProxy) OnConfigChange(oldConfig, newConfig *model.Config) {
    90  	if *oldConfig.ImageProxySettings.Enable != *newConfig.ImageProxySettings.Enable ||
    91  		*oldConfig.ImageProxySettings.ImageProxyType != *newConfig.ImageProxySettings.ImageProxyType {
    92  		proxy.lock.Lock()
    93  		defer proxy.lock.Unlock()
    94  
    95  		proxy.backend = proxy.makeBackend(*newConfig.ImageProxySettings.Enable, *newConfig.ImageProxySettings.ImageProxyType)
    96  	}
    97  }
    98  
    99  // GetImage takes an HTTP request for an image and requests that image using the image proxy.
   100  func (proxy *ImageProxy) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) {
   101  	proxy.lock.RLock()
   102  	defer proxy.lock.RUnlock()
   103  
   104  	if proxy.backend == nil {
   105  		w.WriteHeader(http.StatusNotImplemented)
   106  		return
   107  	}
   108  
   109  	proxy.backend.GetImage(w, r, imageURL)
   110  }
   111  
   112  // GetImageDirect takes the URL of an image and returns the image along with its content type.
   113  func (proxy *ImageProxy) GetImageDirect(imageURL string) (io.ReadCloser, string, error) {
   114  	proxy.lock.RLock()
   115  	defer proxy.lock.RUnlock()
   116  
   117  	if proxy.backend == nil {
   118  		return nil, "", ErrNotEnabled
   119  	}
   120  
   121  	return proxy.backend.GetImageDirect(imageURL)
   122  }
   123  
   124  // GetProxiedImageURL takes the URL of an image and returns a URL that can be used to view that image through the
   125  // image proxy.
   126  func (proxy *ImageProxy) GetProxiedImageURL(imageURL string) string {
   127  	if imageURL == "" || proxy.siteURL == nil {
   128  		return imageURL
   129  	}
   130  	// Parse url, return siteURL in case of failure.
   131  	// Also if the URL is opaque.
   132  	parsedURL, err := url.Parse(imageURL)
   133  	if err != nil || parsedURL.Opaque != "" {
   134  		return proxy.siteURL.String()
   135  	}
   136  	// If host is same as siteURL host, return.
   137  	if parsedURL.Host == proxy.siteURL.Host {
   138  		return parsedURL.String()
   139  	}
   140  
   141  	// Handle protocol-relative URLs.
   142  	if parsedURL.Scheme == "" {
   143  		parsedURL.Scheme = proxy.siteURL.Scheme
   144  	}
   145  
   146  	// If it's a relative URL, fill up the hostname and return.
   147  	if parsedURL.Host == "" {
   148  		parsedURL.Host = proxy.siteURL.Host
   149  		return parsedURL.String()
   150  	}
   151  
   152  	return proxy.siteURL.String() + "/api/v4/image?url=" + url.QueryEscape(parsedURL.String())
   153  }
   154  
   155  // GetUnproxiedImageURL takes the URL of an image on the image proxy and returns the original URL of the image.
   156  func (proxy *ImageProxy) GetUnproxiedImageURL(proxiedURL string) string {
   157  	return getUnproxiedImageURL(proxiedURL, *proxy.ConfigService.Config().ServiceSettings.SiteURL)
   158  }
   159  
   160  func getUnproxiedImageURL(proxiedURL, siteURL string) string {
   161  	if !strings.HasPrefix(proxiedURL, siteURL+"/api/v4/image?url=") {
   162  		return proxiedURL
   163  	}
   164  
   165  	parsed, err := url.Parse(proxiedURL)
   166  	if err != nil {
   167  		return proxiedURL
   168  	}
   169  
   170  	u := parsed.Query()["url"]
   171  	if len(u) == 0 {
   172  		return proxiedURL
   173  	}
   174  
   175  	return u[0]
   176  }