github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/avatars/srv.go (about) 1 package avatars 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "net/url" 12 "os" 13 "runtime" 14 "time" 15 16 "github.com/keybase/client/go/kbhttp/manager" 17 "github.com/keybase/client/go/protocol/keybase1" 18 19 "github.com/keybase/client/go/libkb" 20 ) 21 22 var avatarTransport = &http.Transport{ 23 DialContext: (&net.Dialer{ 24 Timeout: 30 * time.Second, 25 KeepAlive: 30 * time.Second, 26 DualStack: true, 27 }).DialContext, 28 MaxConnsPerHost: 10, 29 MaxIdleConns: 100, 30 IdleConnTimeout: 90 * time.Second, 31 TLSHandshakeTimeout: 10 * time.Second, 32 ExpectContinueTimeout: 1 * time.Second, 33 } 34 35 type Srv struct { 36 libkb.Contextified 37 38 httpSrv *manager.Srv 39 source libkb.AvatarLoaderSource 40 } 41 42 func NewSrv(g *libkb.GlobalContext, httpSrv *manager.Srv, source libkb.AvatarLoaderSource) *Srv { 43 s := &Srv{ 44 Contextified: libkb.NewContextified(g), 45 httpSrv: httpSrv, 46 source: source, 47 } 48 s.httpSrv.HandleFunc("av", manager.SrvTokenModeDefault, s.serve) 49 return s 50 } 51 52 func (s *Srv) GetUserAvatar(username string) (string, error) { 53 if s.httpSrv == nil { 54 return "", fmt.Errorf("HttpSrv is not ready") 55 } 56 57 addr, err := s.httpSrv.Addr() 58 if err != nil { 59 return "", err 60 } 61 62 token := s.httpSrv.Token() 63 64 return fmt.Sprintf("http://%v/av?typ=user&name=%v&format=square_192&token=%v", addr, username, token), nil 65 } 66 67 func (s *Srv) debug(msg string, args ...interface{}) { 68 s.G().GetLog().Debug("Avatars.Srv: %s", fmt.Sprintf(msg, args...)) 69 } 70 71 func (s *Srv) makeError(w http.ResponseWriter, code int, msg string, 72 args ...interface{}) { 73 s.debug("serve: error code: %d msg %s", code, fmt.Sprintf(msg, args...)) 74 w.WriteHeader(code) 75 } 76 77 func (s *Srv) loadFromURL(raw string) (io.ReadCloser, error) { 78 parsed, err := url.Parse(raw) 79 if err != nil { 80 return nil, err 81 } 82 switch parsed.Scheme { 83 case "http", "https": 84 avatarTransport.Proxy = libkb.MakeProxy(s.G().GetEnv()) 85 xprt := libkb.NewInstrumentedRoundTripper(s.G(), func(*http.Request) string { return "AvatarSrv" }, 86 libkb.NewClosingRoundTripper(avatarTransport)) 87 cli := &http.Client{ 88 Transport: xprt, 89 } 90 resp, err := cli.Get(raw) 91 if err != nil { 92 return nil, err 93 } 94 return resp.Body, nil 95 case "file": 96 filePath := parsed.Path 97 if runtime.GOOS == "windows" && len(filePath) > 0 { 98 filePath = filePath[1:] 99 } 100 return os.Open(filePath) 101 default: 102 return nil, fmt.Errorf("unknown URL scheme: %s raw: %s", parsed.Scheme, raw) 103 } 104 } 105 106 func (s *Srv) loadPlaceholder(format keybase1.AvatarFormat, placeholderMap map[keybase1.AvatarFormat]string) ([]byte, error) { 107 encoded, ok := placeholderMap[format] 108 if !ok { 109 return nil, errors.New("no placeholder for format") 110 } 111 return base64.StdEncoding.DecodeString(encoded) 112 } 113 114 func (s *Srv) serve(w http.ResponseWriter, req *http.Request) { 115 typ := req.URL.Query().Get("typ") 116 name := req.URL.Query().Get("name") 117 format := keybase1.AvatarFormat(req.URL.Query().Get("format")) 118 mode := req.URL.Query().Get("mode") 119 mctx := libkb.NewMetaContextBackground(s.G()) 120 121 var loadFn func(libkb.MetaContext, []string, []keybase1.AvatarFormat) (keybase1.LoadAvatarsRes, error) 122 var placeholderMap map[keybase1.AvatarFormat]string 123 switch typ { 124 case "user": 125 loadFn = s.source.LoadUsers 126 placeholderMap = userPlaceholders 127 if mode == "dark" { 128 placeholderMap = userPlaceholdersDark 129 } 130 case "team": 131 loadFn = s.source.LoadTeams 132 placeholderMap = teamPlaceholders 133 if mode == "dark" { 134 placeholderMap = teamPlaceholdersDark 135 } 136 default: 137 s.makeError(w, http.StatusBadRequest, "unknown avatar type: %s", typ) 138 return 139 } 140 res, err := loadFn(mctx, []string{name}, []keybase1.AvatarFormat{format}) 141 if err != nil { 142 s.makeError(w, http.StatusInternalServerError, "failed to load: %s", err) 143 return 144 } 145 nameRes := res.Picmap[name] 146 if nameRes == nil { 147 s.makeError(w, http.StatusInternalServerError, "avatar not loaded") 148 return 149 } 150 var reader io.ReadCloser 151 url, ok := nameRes[format] 152 if !ok || len(url.String()) == 0 { 153 placeholder, err := s.loadPlaceholder(format, placeholderMap) 154 if err != nil { 155 s.makeError(w, http.StatusInternalServerError, "failed to load placeholder: %s", err) 156 return 157 } 158 reader = io.NopCloser(bytes.NewReader(placeholder)) 159 } else { 160 if reader, err = s.loadFromURL(url.String()); err != nil { 161 s.makeError(w, http.StatusInternalServerError, "failed to get URL reader: %s", err) 162 return 163 } 164 } 165 defer reader.Close() 166 if _, err := io.Copy(w, reader); err != nil { 167 s.makeError(w, http.StatusInternalServerError, "failed to write response: %s", err) 168 return 169 } 170 }