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 }