github.com/trigonella/mattermost-server@v5.11.1+incompatible/plugin/client_rpc.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 //go:generate go run interface_generator/main.go 5 6 package plugin 7 8 import ( 9 "bytes" 10 "encoding/gob" 11 "encoding/json" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "log" 16 "net/http" 17 "net/rpc" 18 "os" 19 "reflect" 20 21 "github.com/dyatlov/go-opengraph/opengraph" 22 plugin "github.com/hashicorp/go-plugin" 23 "github.com/mattermost/mattermost-server/mlog" 24 "github.com/mattermost/mattermost-server/model" 25 ) 26 27 var hookNameToId map[string]int = make(map[string]int) 28 29 type hooksRPCClient struct { 30 client *rpc.Client 31 log *mlog.Logger 32 muxBroker *plugin.MuxBroker 33 apiImpl API 34 implemented [TotalHooksId]bool 35 } 36 37 type hooksRPCServer struct { 38 impl interface{} 39 muxBroker *plugin.MuxBroker 40 apiRPCClient *apiRPCClient 41 } 42 43 // Implements hashicorp/go-plugin/plugin.Plugin interface to connect the hooks of a plugin 44 type hooksPlugin struct { 45 hooks interface{} 46 apiImpl API 47 log *mlog.Logger 48 } 49 50 func (p *hooksPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { 51 return &hooksRPCServer{impl: p.hooks, muxBroker: b}, nil 52 } 53 54 func (p *hooksPlugin) Client(b *plugin.MuxBroker, client *rpc.Client) (interface{}, error) { 55 return &hooksRPCClient{client: client, log: p.log, muxBroker: b, apiImpl: p.apiImpl}, nil 56 } 57 58 type apiRPCClient struct { 59 client *rpc.Client 60 } 61 62 type apiRPCServer struct { 63 impl API 64 } 65 66 // ErrorString is a fallback for sending unregistered implementations of the error interface across 67 // rpc. For example, the errorString type from the github.com/pkg/errors package cannot be 68 // registered since it is not exported, but this precludes common error handling paradigms. 69 // ErrorString merely preserves the string description of the error, while satisfying the error 70 // interface itself to allow other registered types (such as model.AppError) to be sent unmodified. 71 type ErrorString struct { 72 Err string 73 } 74 75 func (e ErrorString) Error() string { 76 return e.Err 77 } 78 79 func encodableError(err error) error { 80 if err == nil { 81 return nil 82 } 83 if _, ok := err.(*model.AppError); ok { 84 return err 85 } 86 87 return &ErrorString{ 88 Err: err.Error(), 89 } 90 } 91 92 // Registering some types used by MM for encoding/gob used by rpc 93 func init() { 94 gob.Register([]*model.SlackAttachment{}) 95 gob.Register([]interface{}{}) 96 gob.Register(map[string]interface{}{}) 97 gob.Register(&model.AppError{}) 98 gob.Register(&ErrorString{}) 99 gob.Register(&opengraph.OpenGraph{}) 100 } 101 102 // These enforce compile time checks to make sure types implement the interface 103 // If you are getting an error here, you probably need to run `make pluginapi` to 104 // autogenerate RPC glue code 105 var _ plugin.Plugin = &hooksPlugin{} 106 var _ Hooks = &hooksRPCClient{} 107 108 // 109 // Below are specal cases for hooks or APIs that can not be auto generated 110 // 111 112 func (g *hooksRPCClient) Implemented() (impl []string, err error) { 113 err = g.client.Call("Plugin.Implemented", struct{}{}, &impl) 114 for _, hookName := range impl { 115 if hookId, ok := hookNameToId[hookName]; ok { 116 g.implemented[hookId] = true 117 } 118 } 119 return 120 } 121 122 // Implemented replies with the names of the hooks that are implemented. 123 func (s *hooksRPCServer) Implemented(args struct{}, reply *[]string) error { 124 ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem() 125 implType := reflect.TypeOf(s.impl) 126 selfType := reflect.TypeOf(s) 127 var methods []string 128 for i := 0; i < ifaceType.NumMethod(); i++ { 129 method := ifaceType.Method(i) 130 if m, ok := implType.MethodByName(method.Name); !ok { 131 continue 132 } else if m.Type.NumIn() != method.Type.NumIn()+1 { 133 continue 134 } else if m.Type.NumOut() != method.Type.NumOut() { 135 continue 136 } else { 137 match := true 138 for j := 0; j < method.Type.NumIn(); j++ { 139 if m.Type.In(j+1) != method.Type.In(j) { 140 match = false 141 break 142 } 143 } 144 for j := 0; j < method.Type.NumOut(); j++ { 145 if m.Type.Out(j) != method.Type.Out(j) { 146 match = false 147 break 148 } 149 } 150 if !match { 151 continue 152 } 153 } 154 if _, ok := selfType.MethodByName(method.Name); !ok { 155 continue 156 } 157 methods = append(methods, method.Name) 158 } 159 *reply = methods 160 return encodableError(nil) 161 } 162 163 type Z_OnActivateArgs struct { 164 APIMuxId uint32 165 } 166 167 type Z_OnActivateReturns struct { 168 A error 169 } 170 171 func (g *hooksRPCClient) OnActivate() error { 172 muxId := g.muxBroker.NextId() 173 go g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{ 174 impl: g.apiImpl, 175 }) 176 177 _args := &Z_OnActivateArgs{ 178 APIMuxId: muxId, 179 } 180 _returns := &Z_OnActivateReturns{} 181 182 if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil { 183 g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err)) 184 } 185 return _returns.A 186 } 187 188 func (s *hooksRPCServer) OnActivate(args *Z_OnActivateArgs, returns *Z_OnActivateReturns) error { 189 connection, err := s.muxBroker.Dial(args.APIMuxId) 190 if err != nil { 191 return err 192 } 193 194 s.apiRPCClient = &apiRPCClient{ 195 client: rpc.NewClient(connection), 196 } 197 198 if mmplugin, ok := s.impl.(interface { 199 SetAPI(api API) 200 }); ok { 201 mmplugin.SetAPI(s.apiRPCClient) 202 } 203 204 if mmplugin, ok := s.impl.(interface { 205 OnConfigurationChange() error 206 }); ok { 207 if err := mmplugin.OnConfigurationChange(); err != nil { 208 fmt.Fprintf(os.Stderr, "[ERROR] call to OnConfigurationChange failed, error: %v", err.Error()) 209 } 210 } 211 212 // Capture output of standard logger because go-plugin 213 // redirects it. 214 log.SetOutput(os.Stderr) 215 216 if hook, ok := s.impl.(interface { 217 OnActivate() error 218 }); ok { 219 returns.A = encodableError(hook.OnActivate()) 220 } 221 return nil 222 } 223 224 type Z_LoadPluginConfigurationArgsArgs struct { 225 } 226 227 type Z_LoadPluginConfigurationArgsReturns struct { 228 A []byte 229 } 230 231 func (g *apiRPCClient) LoadPluginConfiguration(dest interface{}) error { 232 _args := &Z_LoadPluginConfigurationArgsArgs{} 233 _returns := &Z_LoadPluginConfigurationArgsReturns{} 234 if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil { 235 log.Printf("RPC call to LoadPluginConfiguration API failed: %s", err.Error()) 236 } 237 if err := json.Unmarshal(_returns.A, dest); err != nil { 238 log.Printf("LoadPluginConfiguration API failed to unmarshal: %s", err.Error()) 239 } 240 return nil 241 } 242 243 func (s *apiRPCServer) LoadPluginConfiguration(args *Z_LoadPluginConfigurationArgsArgs, returns *Z_LoadPluginConfigurationArgsReturns) error { 244 var config interface{} 245 if hook, ok := s.impl.(interface { 246 LoadPluginConfiguration(dest interface{}) error 247 }); ok { 248 if err := hook.LoadPluginConfiguration(&config); err != nil { 249 return err 250 } 251 } 252 b, err := json.Marshal(config) 253 if err != nil { 254 return err 255 } 256 returns.A = b 257 return nil 258 } 259 260 func init() { 261 hookNameToId["ServeHTTP"] = ServeHTTPId 262 } 263 264 type Z_ServeHTTPArgs struct { 265 ResponseWriterStream uint32 266 Request *http.Request 267 Context *Context 268 RequestBodyStream uint32 269 } 270 271 func (g *hooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) { 272 if !g.implemented[ServeHTTPId] { 273 http.NotFound(w, r) 274 return 275 } 276 277 serveHTTPStreamId := g.muxBroker.NextId() 278 go func() { 279 connection, err := g.muxBroker.Accept(serveHTTPStreamId) 280 if err != nil { 281 g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't accept connection", mlog.Uint32("serve_http_stream_id", serveHTTPStreamId), mlog.Err(err)) 282 return 283 } 284 defer connection.Close() 285 286 rpcServer := rpc.NewServer() 287 if err := rpcServer.RegisterName("Plugin", &httpResponseWriterRPCServer{w: w}); err != nil { 288 g.log.Error("Plugin failed to ServeHTTP, coulden't register RPC name", mlog.Err(err)) 289 return 290 } 291 rpcServer.ServeConn(connection) 292 }() 293 294 requestBodyStreamId := uint32(0) 295 if r.Body != nil { 296 requestBodyStreamId = g.muxBroker.NextId() 297 go func() { 298 bodyConnection, err := g.muxBroker.Accept(requestBodyStreamId) 299 if err != nil { 300 g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't Accept request body connection", mlog.Err(err)) 301 return 302 } 303 defer bodyConnection.Close() 304 serveIOReader(r.Body, bodyConnection) 305 }() 306 } 307 308 forwardedRequest := &http.Request{ 309 Method: r.Method, 310 URL: r.URL, 311 Proto: r.Proto, 312 ProtoMajor: r.ProtoMajor, 313 ProtoMinor: r.ProtoMinor, 314 Header: r.Header, 315 Host: r.Host, 316 RemoteAddr: r.RemoteAddr, 317 RequestURI: r.RequestURI, 318 } 319 320 if err := g.client.Call("Plugin.ServeHTTP", Z_ServeHTTPArgs{ 321 Context: c, 322 ResponseWriterStream: serveHTTPStreamId, 323 Request: forwardedRequest, 324 RequestBodyStream: requestBodyStreamId, 325 }, nil); err != nil { 326 g.log.Error("Plugin failed to ServeHTTP, RPC call failed", mlog.Err(err)) 327 http.Error(w, "500 internal server error", http.StatusInternalServerError) 328 } 329 } 330 331 func (s *hooksRPCServer) ServeHTTP(args *Z_ServeHTTPArgs, returns *struct{}) error { 332 connection, err := s.muxBroker.Dial(args.ResponseWriterStream) 333 if err != nil { 334 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error()) 335 return err 336 } 337 w := connectHTTPResponseWriter(connection) 338 defer w.Close() 339 340 r := args.Request 341 if args.RequestBodyStream != 0 { 342 connection, err := s.muxBroker.Dial(args.RequestBodyStream) 343 if err != nil { 344 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error()) 345 return err 346 } 347 r.Body = connectIOReader(connection) 348 } else { 349 r.Body = ioutil.NopCloser(&bytes.Buffer{}) 350 } 351 defer r.Body.Close() 352 353 if hook, ok := s.impl.(interface { 354 ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) 355 }); ok { 356 hook.ServeHTTP(args.Context, w, r) 357 } else { 358 http.NotFound(w, r) 359 } 360 361 return nil 362 } 363 364 func init() { 365 hookNameToId["FileWillBeUploaded"] = FileWillBeUploadedId 366 } 367 368 type Z_FileWillBeUploadedArgs struct { 369 A *Context 370 B *model.FileInfo 371 UploadedFileStream uint32 372 ReplacementFileStream uint32 373 } 374 375 type Z_FileWillBeUploadedReturns struct { 376 A *model.FileInfo 377 B string 378 } 379 380 func (g *hooksRPCClient) FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { 381 if !g.implemented[FileWillBeUploadedId] { 382 return info, "" 383 } 384 385 uploadedFileStreamId := g.muxBroker.NextId() 386 go func() { 387 uploadedFileConnection, err := g.muxBroker.Accept(uploadedFileStreamId) 388 if err != nil { 389 g.log.Error("Plugin failed to serve upload file stream. MuxBroker could not Accept connection", mlog.Err(err)) 390 return 391 } 392 defer uploadedFileConnection.Close() 393 serveIOReader(file, uploadedFileConnection) 394 }() 395 396 replacementFileStreamId := g.muxBroker.NextId() 397 go func() { 398 replacementFileConnection, err := g.muxBroker.Accept(replacementFileStreamId) 399 if err != nil { 400 g.log.Error("Plugin failed to serve replacement file stream. MuxBroker could not Accept connection", mlog.Err(err)) 401 return 402 } 403 defer replacementFileConnection.Close() 404 if _, err := io.Copy(output, replacementFileConnection); err != nil && err != io.EOF { 405 g.log.Error("Error reading replacement file.", mlog.Err(err)) 406 } 407 }() 408 409 _args := &Z_FileWillBeUploadedArgs{c, info, uploadedFileStreamId, replacementFileStreamId} 410 _returns := &Z_FileWillBeUploadedReturns{A: _args.B} 411 if g.implemented[FileWillBeUploadedId] { 412 if err := g.client.Call("Plugin.FileWillBeUploaded", _args, _returns); err != nil { 413 g.log.Error("RPC call FileWillBeUploaded to plugin failed.", mlog.Err(err)) 414 } 415 } 416 return _returns.A, _returns.B 417 } 418 419 func (s *hooksRPCServer) FileWillBeUploaded(args *Z_FileWillBeUploadedArgs, returns *Z_FileWillBeUploadedReturns) error { 420 uploadFileConnection, err := s.muxBroker.Dial(args.UploadedFileStream) 421 if err != nil { 422 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote upload file stream, error: %v", err.Error()) 423 return err 424 } 425 defer uploadFileConnection.Close() 426 fileReader := connectIOReader(uploadFileConnection) 427 defer fileReader.Close() 428 429 replacementFileConnection, err := s.muxBroker.Dial(args.ReplacementFileStream) 430 if err != nil { 431 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote replacement file stream, error: %v", err.Error()) 432 return err 433 } 434 defer replacementFileConnection.Close() 435 returnFileWriter := replacementFileConnection 436 437 if hook, ok := s.impl.(interface { 438 FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) 439 }); ok { 440 returns.A, returns.B = hook.FileWillBeUploaded(args.A, args.B, fileReader, returnFileWriter) 441 } else { 442 return fmt.Errorf("Hook FileWillBeUploaded called but not implemented.") 443 } 444 return nil 445 } 446 447 // MessageWillBePosted is in this file because of the difficulty of identifiying which fields need special behaviour. 448 // The special behaviour needed is decoding the returned post into the original one to avoid the unintentional removal 449 // of fields by older plugins. 450 func init() { 451 hookNameToId["MessageWillBePosted"] = MessageWillBePostedId 452 } 453 454 type Z_MessageWillBePostedArgs struct { 455 A *Context 456 B *model.Post 457 } 458 459 type Z_MessageWillBePostedReturns struct { 460 A *model.Post 461 B string 462 } 463 464 func (g *hooksRPCClient) MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string) { 465 _args := &Z_MessageWillBePostedArgs{c, post} 466 _returns := &Z_MessageWillBePostedReturns{A: _args.B} 467 if g.implemented[MessageWillBePostedId] { 468 if err := g.client.Call("Plugin.MessageWillBePosted", _args, _returns); err != nil { 469 g.log.Error("RPC call MessageWillBePosted to plugin failed.", mlog.Err(err)) 470 } 471 } 472 return _returns.A, _returns.B 473 } 474 475 func (s *hooksRPCServer) MessageWillBePosted(args *Z_MessageWillBePostedArgs, returns *Z_MessageWillBePostedReturns) error { 476 if hook, ok := s.impl.(interface { 477 MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string) 478 }); ok { 479 returns.A, returns.B = hook.MessageWillBePosted(args.A, args.B) 480 481 } else { 482 return encodableError(fmt.Errorf("Hook MessageWillBePosted called but not implemented.")) 483 } 484 return nil 485 } 486 487 // MessageWillBeUpdated is in this file because of the difficulty of identifiying which fields need special behaviour. 488 // The special behavour needed is decoding the returned post into the original one to avoid the unintentional removal 489 // of fields by older plugins. 490 func init() { 491 hookNameToId["MessageWillBeUpdated"] = MessageWillBeUpdatedId 492 } 493 494 type Z_MessageWillBeUpdatedArgs struct { 495 A *Context 496 B *model.Post 497 C *model.Post 498 } 499 500 type Z_MessageWillBeUpdatedReturns struct { 501 A *model.Post 502 B string 503 } 504 505 func (g *hooksRPCClient) MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string) { 506 _args := &Z_MessageWillBeUpdatedArgs{c, newPost, oldPost} 507 _returns := &Z_MessageWillBeUpdatedReturns{A: _args.B} 508 if g.implemented[MessageWillBeUpdatedId] { 509 if err := g.client.Call("Plugin.MessageWillBeUpdated", _args, _returns); err != nil { 510 g.log.Error("RPC call MessageWillBeUpdated to plugin failed.", mlog.Err(err)) 511 } 512 } 513 return _returns.A, _returns.B 514 } 515 516 func (s *hooksRPCServer) MessageWillBeUpdated(args *Z_MessageWillBeUpdatedArgs, returns *Z_MessageWillBeUpdatedReturns) error { 517 if hook, ok := s.impl.(interface { 518 MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string) 519 }); ok { 520 returns.A, returns.B = hook.MessageWillBeUpdated(args.A, args.B, args.C) 521 522 } else { 523 return encodableError(fmt.Errorf("Hook MessageWillBeUpdated called but not implemented.")) 524 } 525 return nil 526 }