github.com/levb/mattermost-server@v5.3.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/hashicorp/go-plugin"
    22  	"github.com/mattermost/mattermost-server/mlog"
    23  	"github.com/mattermost/mattermost-server/model"
    24  )
    25  
    26  var hookNameToId map[string]int = make(map[string]int)
    27  
    28  type hooksRPCClient struct {
    29  	client      *rpc.Client
    30  	log         *mlog.Logger
    31  	muxBroker   *plugin.MuxBroker
    32  	apiImpl     API
    33  	implemented [TotalHooksId]bool
    34  }
    35  
    36  type hooksRPCServer struct {
    37  	impl         interface{}
    38  	muxBroker    *plugin.MuxBroker
    39  	apiRPCClient *apiRPCClient
    40  }
    41  
    42  // Implements hashicorp/go-plugin/plugin.Plugin interface to connect the hooks of a plugin
    43  type hooksPlugin struct {
    44  	hooks   interface{}
    45  	apiImpl API
    46  	log     *mlog.Logger
    47  }
    48  
    49  func (p *hooksPlugin) Server(b *plugin.MuxBroker) (interface{}, error) {
    50  	return &hooksRPCServer{impl: p.hooks, muxBroker: b}, nil
    51  }
    52  
    53  func (p *hooksPlugin) Client(b *plugin.MuxBroker, client *rpc.Client) (interface{}, error) {
    54  	return &hooksRPCClient{client: client, log: p.log, muxBroker: b, apiImpl: p.apiImpl}, nil
    55  }
    56  
    57  type apiRPCClient struct {
    58  	client *rpc.Client
    59  }
    60  
    61  type apiRPCServer struct {
    62  	impl API
    63  }
    64  
    65  // Registering some types used by MM for encoding/gob used by rpc
    66  func init() {
    67  	gob.Register([]*model.SlackAttachment{})
    68  	gob.Register([]interface{}{})
    69  	gob.Register(map[string]interface{}{})
    70  	gob.Register(&model.AppError{})
    71  }
    72  
    73  // These enforce compile time checks to make sure types implement the interface
    74  // If you are getting an error here, you probably need to run `make pluginapi` to
    75  // autogenerate RPC glue code
    76  var _ plugin.Plugin = &hooksPlugin{}
    77  var _ Hooks = &hooksRPCClient{}
    78  
    79  //
    80  // Below are specal cases for hooks or APIs that can not be auto generated
    81  //
    82  
    83  func (g *hooksRPCClient) Implemented() (impl []string, err error) {
    84  	err = g.client.Call("Plugin.Implemented", struct{}{}, &impl)
    85  	for _, hookName := range impl {
    86  		if hookId, ok := hookNameToId[hookName]; ok {
    87  			g.implemented[hookId] = true
    88  		}
    89  	}
    90  	return
    91  }
    92  
    93  // Implemented replies with the names of the hooks that are implemented.
    94  func (s *hooksRPCServer) Implemented(args struct{}, reply *[]string) error {
    95  	ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem()
    96  	implType := reflect.TypeOf(s.impl)
    97  	selfType := reflect.TypeOf(s)
    98  	var methods []string
    99  	for i := 0; i < ifaceType.NumMethod(); i++ {
   100  		method := ifaceType.Method(i)
   101  		if m, ok := implType.MethodByName(method.Name); !ok {
   102  			continue
   103  		} else if m.Type.NumIn() != method.Type.NumIn()+1 {
   104  			continue
   105  		} else if m.Type.NumOut() != method.Type.NumOut() {
   106  			continue
   107  		} else {
   108  			match := true
   109  			for j := 0; j < method.Type.NumIn(); j++ {
   110  				if m.Type.In(j+1) != method.Type.In(j) {
   111  					match = false
   112  					break
   113  				}
   114  			}
   115  			for j := 0; j < method.Type.NumOut(); j++ {
   116  				if m.Type.Out(j) != method.Type.Out(j) {
   117  					match = false
   118  					break
   119  				}
   120  			}
   121  			if !match {
   122  				continue
   123  			}
   124  		}
   125  		if _, ok := selfType.MethodByName(method.Name); !ok {
   126  			continue
   127  		}
   128  		methods = append(methods, method.Name)
   129  	}
   130  	*reply = methods
   131  	return nil
   132  }
   133  
   134  type Z_OnActivateArgs struct {
   135  	APIMuxId uint32
   136  }
   137  
   138  type Z_OnActivateReturns struct {
   139  	A error
   140  }
   141  
   142  func (g *hooksRPCClient) OnActivate() error {
   143  	muxId := g.muxBroker.NextId()
   144  	go g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{
   145  		impl: g.apiImpl,
   146  	})
   147  
   148  	_args := &Z_OnActivateArgs{
   149  		APIMuxId: muxId,
   150  	}
   151  	_returns := &Z_OnActivateReturns{}
   152  
   153  	if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil {
   154  		g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err))
   155  	}
   156  	return _returns.A
   157  }
   158  
   159  func (s *hooksRPCServer) OnActivate(args *Z_OnActivateArgs, returns *Z_OnActivateReturns) error {
   160  	connection, err := s.muxBroker.Dial(args.APIMuxId)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	s.apiRPCClient = &apiRPCClient{
   166  		client: rpc.NewClient(connection),
   167  	}
   168  
   169  	if mmplugin, ok := s.impl.(interface {
   170  		SetAPI(api API)
   171  		OnConfigurationChange() error
   172  	}); !ok {
   173  	} else {
   174  		mmplugin.SetAPI(s.apiRPCClient)
   175  		mmplugin.OnConfigurationChange()
   176  	}
   177  
   178  	// Capture output of standard logger because go-plugin
   179  	// redirects it.
   180  	log.SetOutput(os.Stderr)
   181  
   182  	if hook, ok := s.impl.(interface {
   183  		OnActivate() error
   184  	}); ok {
   185  		returns.A = hook.OnActivate()
   186  	}
   187  	return nil
   188  }
   189  
   190  type Z_LoadPluginConfigurationArgsArgs struct {
   191  }
   192  
   193  type Z_LoadPluginConfigurationArgsReturns struct {
   194  	A []byte
   195  }
   196  
   197  func (g *apiRPCClient) LoadPluginConfiguration(dest interface{}) error {
   198  	_args := &Z_LoadPluginConfigurationArgsArgs{}
   199  	_returns := &Z_LoadPluginConfigurationArgsReturns{}
   200  	if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil {
   201  		log.Printf("RPC call to LoadPluginConfiguration API failed: %s", err.Error())
   202  	}
   203  	if err := json.Unmarshal(_returns.A, dest); err != nil {
   204  		log.Printf("LoadPluginConfiguration API failed to unmarshal: %s", err.Error())
   205  	}
   206  	return nil
   207  }
   208  
   209  func (s *apiRPCServer) LoadPluginConfiguration(args *Z_LoadPluginConfigurationArgsArgs, returns *Z_LoadPluginConfigurationArgsReturns) error {
   210  	var config interface{}
   211  	if hook, ok := s.impl.(interface {
   212  		LoadPluginConfiguration(dest interface{}) error
   213  	}); ok {
   214  		if err := hook.LoadPluginConfiguration(&config); err != nil {
   215  			return err
   216  		}
   217  	}
   218  	b, err := json.Marshal(config)
   219  	if err != nil {
   220  		return err
   221  	}
   222  	returns.A = b
   223  	return nil
   224  }
   225  
   226  func init() {
   227  	hookNameToId["ServeHTTP"] = ServeHTTPId
   228  }
   229  
   230  type Z_ServeHTTPArgs struct {
   231  	ResponseWriterStream uint32
   232  	Request              *http.Request
   233  	Context              *Context
   234  	RequestBodyStream    uint32
   235  }
   236  
   237  func (g *hooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) {
   238  	if !g.implemented[ServeHTTPId] {
   239  		http.NotFound(w, r)
   240  		return
   241  	}
   242  
   243  	serveHTTPStreamId := g.muxBroker.NextId()
   244  	go func() {
   245  		connection, err := g.muxBroker.Accept(serveHTTPStreamId)
   246  		if err != nil {
   247  			g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't accept connection", mlog.Uint32("serve_http_stream_id", serveHTTPStreamId), mlog.Err(err))
   248  			http.Error(w, "500 internal server error", http.StatusInternalServerError)
   249  			return
   250  		}
   251  		defer connection.Close()
   252  
   253  		rpcServer := rpc.NewServer()
   254  		if err := rpcServer.RegisterName("Plugin", &httpResponseWriterRPCServer{w: w}); err != nil {
   255  			g.log.Error("Plugin failed to ServeHTTP, coulden't register RPC name", mlog.Err(err))
   256  			http.Error(w, "500 internal server error", http.StatusInternalServerError)
   257  			return
   258  		}
   259  		rpcServer.ServeConn(connection)
   260  	}()
   261  
   262  	requestBodyStreamId := uint32(0)
   263  	if r.Body != nil {
   264  		requestBodyStreamId = g.muxBroker.NextId()
   265  		go func() {
   266  			bodyConnection, err := g.muxBroker.Accept(requestBodyStreamId)
   267  			if err != nil {
   268  				g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't Accept request body connecion", mlog.Err(err))
   269  				http.Error(w, "500 internal server error", http.StatusInternalServerError)
   270  				return
   271  			}
   272  			defer bodyConnection.Close()
   273  			serveIOReader(r.Body, bodyConnection)
   274  		}()
   275  	}
   276  
   277  	forwardedRequest := &http.Request{
   278  		Method:     r.Method,
   279  		URL:        r.URL,
   280  		Proto:      r.Proto,
   281  		ProtoMajor: r.ProtoMajor,
   282  		ProtoMinor: r.ProtoMinor,
   283  		Header:     r.Header,
   284  		Host:       r.Host,
   285  		RemoteAddr: r.RemoteAddr,
   286  		RequestURI: r.RequestURI,
   287  	}
   288  
   289  	if err := g.client.Call("Plugin.ServeHTTP", Z_ServeHTTPArgs{
   290  		Context:              c,
   291  		ResponseWriterStream: serveHTTPStreamId,
   292  		Request:              forwardedRequest,
   293  		RequestBodyStream:    requestBodyStreamId,
   294  	}, nil); err != nil {
   295  		g.log.Error("Plugin failed to ServeHTTP, RPC call failed", mlog.Err(err))
   296  		http.Error(w, "500 internal server error", http.StatusInternalServerError)
   297  	}
   298  	return
   299  }
   300  
   301  func (s *hooksRPCServer) ServeHTTP(args *Z_ServeHTTPArgs, returns *struct{}) error {
   302  	connection, err := s.muxBroker.Dial(args.ResponseWriterStream)
   303  	if err != nil {
   304  		fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error())
   305  		return err
   306  	}
   307  	w := connectHTTPResponseWriter(connection)
   308  	defer w.Close()
   309  
   310  	r := args.Request
   311  	if args.RequestBodyStream != 0 {
   312  		connection, err := s.muxBroker.Dial(args.RequestBodyStream)
   313  		if err != nil {
   314  			fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error())
   315  			return err
   316  		}
   317  		r.Body = connectIOReader(connection)
   318  	} else {
   319  		r.Body = ioutil.NopCloser(&bytes.Buffer{})
   320  	}
   321  	defer r.Body.Close()
   322  
   323  	if hook, ok := s.impl.(interface {
   324  		ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request)
   325  	}); ok {
   326  		hook.ServeHTTP(args.Context, w, r)
   327  	} else {
   328  		http.NotFound(w, r)
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  func init() {
   335  	hookNameToId["FileWillBeUploaded"] = FileWillBeUploadedId
   336  }
   337  
   338  type Z_FileWillBeUploadedArgs struct {
   339  	A                     *Context
   340  	B                     *model.FileInfo
   341  	UploadedFileStream    uint32
   342  	ReplacementFileStream uint32
   343  }
   344  
   345  type Z_FileWillBeUploadedReturns struct {
   346  	A *model.FileInfo
   347  	B string
   348  }
   349  
   350  func (g *hooksRPCClient) FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
   351  	if !g.implemented[FileWillBeUploadedId] {
   352  		return info, ""
   353  	}
   354  
   355  	uploadedFileStreamId := g.muxBroker.NextId()
   356  	go func() {
   357  		uploadedFileConnection, err := g.muxBroker.Accept(uploadedFileStreamId)
   358  		if err != nil {
   359  			g.log.Error("Plugin failed to serve upload file stream. MuxBroker could not Accept connection", mlog.Err(err))
   360  			return
   361  		}
   362  		defer uploadedFileConnection.Close()
   363  		serveIOReader(file, uploadedFileConnection)
   364  	}()
   365  
   366  	replacementFileStreamId := g.muxBroker.NextId()
   367  	go func() {
   368  		replacementFileConnection, err := g.muxBroker.Accept(replacementFileStreamId)
   369  		if err != nil {
   370  			g.log.Error("Plugin failed to serve replacement file stream. MuxBroker could not Accept connection", mlog.Err(err))
   371  			return
   372  		}
   373  		defer replacementFileConnection.Close()
   374  		if _, err := io.Copy(output, replacementFileConnection); err != nil && err != io.EOF {
   375  			g.log.Error("Error reading replacement file.", mlog.Err(err))
   376  		}
   377  	}()
   378  
   379  	_args := &Z_FileWillBeUploadedArgs{c, info, uploadedFileStreamId, replacementFileStreamId}
   380  	_returns := &Z_FileWillBeUploadedReturns{}
   381  	if g.implemented[FileWillBeUploadedId] {
   382  		if err := g.client.Call("Plugin.FileWillBeUploaded", _args, _returns); err != nil {
   383  			g.log.Error("RPC call FileWillBeUploaded to plugin failed.", mlog.Err(err))
   384  		}
   385  	}
   386  	return _returns.A, _returns.B
   387  }
   388  
   389  func (s *hooksRPCServer) FileWillBeUploaded(args *Z_FileWillBeUploadedArgs, returns *Z_FileWillBeUploadedReturns) error {
   390  	uploadFileConnection, err := s.muxBroker.Dial(args.UploadedFileStream)
   391  	if err != nil {
   392  		fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote upload file stream, error: %v", err.Error())
   393  		return err
   394  	}
   395  	defer uploadFileConnection.Close()
   396  	fileReader := connectIOReader(uploadFileConnection)
   397  	defer fileReader.Close()
   398  
   399  	replacementFileConnection, err := s.muxBroker.Dial(args.ReplacementFileStream)
   400  	if err != nil {
   401  		fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote replacement file stream, error: %v", err.Error())
   402  		return err
   403  	}
   404  	defer replacementFileConnection.Close()
   405  	returnFileWriter := replacementFileConnection
   406  
   407  	if hook, ok := s.impl.(interface {
   408  		FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string)
   409  	}); ok {
   410  		returns.A, returns.B = hook.FileWillBeUploaded(args.A, args.B, fileReader, returnFileWriter)
   411  	} else {
   412  		return fmt.Errorf("Hook FileWillBeUploaded called but not implemented.")
   413  	}
   414  	return nil
   415  }