github.com/mad-app/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  }