From 6ec77e06eaecc9b0567e1c67e9255da448fa4b2f Mon Sep 17 00:00:00 2001 From: Wim Date: Thu, 23 Jun 2016 20:28:05 +0200 Subject: [PATCH] Sync with mattermost 3.1.0 --- .../42wim/matterbridge-plus/bridge/bridge.go | 169 +++++----- .../42wim/matterbridge-plus/bridge/config.go | 3 + .../matterclient/matterclient.go | 167 ++++++++-- .../42wim/matterbridge/matterhook/LICENSE | 202 ------------ .../matterbridge/matterhook/matterhook.go | 154 --------- .../mattermost/platform/einterfaces/ldap.go | 2 + .../mattermost/platform/model/client.go | 246 ++++++++++++-- .../mattermost/platform/model/command.go | 5 +- .../mattermost/platform/model/config.go | 160 +++++++-- .../mattermost/platform/model/file.go | 4 - .../platform/model/gitlab/gitlab.go | 3 +- .../platform/model/incoming_webhook.go | 135 +++++++- .../mattermost/platform/model/job.go | 91 +++++ .../platform/model/outgoing_webhook.go | 47 +++ .../mattermost/platform/model/preference.go | 3 + .../platform/model/search_params.go | 8 +- .../mattermost/platform/model/user.go | 18 +- .../mattermost/platform/model/utils.go | 5 +- .../mattermost/platform/model/version.go | 2 + vendor/golang.org/x/net/lex/httplex/LICENSE | 27 ++ .../golang.org/x/net/lex/httplex/httplex.go | 312 ++++++++++++++++++ vendor/manifest | 30 +- 22 files changed, 1237 insertions(+), 556 deletions(-) delete mode 100644 vendor/github.com/42wim/matterbridge/matterhook/LICENSE delete mode 100644 vendor/github.com/42wim/matterbridge/matterhook/matterhook.go create mode 100644 vendor/github.com/mattermost/platform/model/job.go create mode 100644 vendor/golang.org/x/net/lex/httplex/LICENSE create mode 100644 vendor/golang.org/x/net/lex/httplex/httplex.go diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go b/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go index ebd5456d..ee27a65d 100644 --- a/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go +++ b/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go @@ -21,15 +21,17 @@ type MMhook struct { } type MMapi struct { - mc *matterclient.MMClient - mmMap map[string]string + mc *matterclient.MMClient + mmMap map[string]string + mmIgnoreNicks []string } type MMirc struct { - i *irc.Connection - ircNick string - ircMap map[string]string - names map[string][]string + i *irc.Connection + ircNick string + ircMap map[string]string + names map[string][]string + ircIgnoreNicks []string } type MMMessage struct { @@ -53,6 +55,8 @@ type FancyLog struct { var flog FancyLog +const Legacy = "legacy" + func initFLog() { flog.irc = log.WithFields(log.Fields{"module": "irc"}) flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) @@ -66,7 +70,9 @@ func NewBridge(name string, config *Config, kind string) *Bridge { b.ircNick = b.Config.IRC.Nick b.ircMap = make(map[string]string) b.MMirc.names = make(map[string][]string) - if kind == "legacy" { + b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks) + b.mmIgnoreNicks = strings.Fields(b.Config.Mattermost.IgnoreNicks) + if kind == Legacy { if len(b.Config.Token) > 0 { for _, val := range b.Config.Token { b.ircMap[val.IRCChannel] = val.MMChannel @@ -87,10 +93,14 @@ func NewBridge(name string, config *Config, kind string) *Bridge { } b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password, b.Config.Mattermost.Team, b.Config.Mattermost.Server) + b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify + b.mc.NoTLS = b.Config.Mattermost.NoTLS + flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server) err := b.mc.Login() if err != nil { - flog.mm.Fatal("can not connect", err) + flog.mm.Fatal("Can not connect", err) } + flog.mm.Info("Login ok") b.mc.JoinChannel(b.Config.Mattermost.Channel) if len(b.Config.Channel) > 0 { for _, val := range b.Config.Channel { @@ -99,7 +109,9 @@ func NewBridge(name string, config *Config, kind string) *Bridge { } go b.mc.WsReceiver() } + flog.irc.Info("Trying IRC connection") b.i = b.createIRC(name) + flog.irc.Info("Connection succeeded") go b.handleMatter() return b } @@ -111,37 +123,51 @@ func (b *Bridge) createIRC(name string) *irc.Connection { if b.Config.IRC.Password != "" { i.Password = b.Config.IRC.Password } - i.AddCallback("*", b.handleOther) + i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) i.Connect(b.Config.IRC.Server + ":" + strconv.Itoa(b.Config.IRC.Port)) return i } func (b *Bridge) handleNewConnection(event *irc.Event) { + flog.irc.Info("Registering callbacks") + i := b.i b.ircNick = event.Arguments[0] + i.AddCallback("PRIVMSG", b.handlePrivMsg) + i.AddCallback("CTCP_ACTION", b.handlePrivMsg) + i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) + i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) + i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) + i.AddCallback(ircm.NOTICE, b.handleNotice) + i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) + i.AddCallback("PING", func(e *irc.Event) { + i.SendRaw("PONG :" + e.Message()) + flog.irc.Debugf("PING/PONG") + }) + if b.Config.Mattermost.ShowJoinPart { + i.AddCallback("JOIN", b.handleJoinPart) + i.AddCallback("PART", b.handleJoinPart) + } + i.AddCallback("*", b.handleOther) b.setupChannels() } func (b *Bridge) setupChannels() { i := b.i - flog.irc.Info("Joining ", b.Config.IRC.Channel, " as ", b.ircNick) - i.Join(b.Config.IRC.Channel) - if b.kind == "legacy" { + if b.Config.IRC.Channel != "" { + flog.irc.Infof("Joining %s as %s", b.Config.IRC.Channel, b.ircNick) + i.Join(b.Config.IRC.Channel) + } + if b.kind == Legacy { for _, val := range b.Config.Token { - flog.irc.Info("Joining ", val.IRCChannel, " as ", b.ircNick) + flog.irc.Infof("Joining %s as %s", val.IRCChannel, b.ircNick) i.Join(val.IRCChannel) } } else { for _, val := range b.Config.Channel { - flog.irc.Info("Joining ", val.IRC, " as ", b.ircNick) + flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) i.Join(val.IRC) } } - i.AddCallback("PRIVMSG", b.handlePrivMsg) - i.AddCallback("CTCP_ACTION", b.handlePrivMsg) - if b.Config.Mattermost.ShowJoinPart { - i.AddCallback("JOIN", b.handleJoinPart) - i.AddCallback("PART", b.handleJoinPart) - } } func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool { @@ -177,6 +203,9 @@ func (b *Bridge) ircNickFormat(nick string) string { } func (b *Bridge) handlePrivMsg(event *irc.Event) { + if b.ignoreMessage(event.Nick, event.Message(), "irc") { + return + } if b.handleIrcBotCommand(event) { return } @@ -238,81 +267,21 @@ func (b *Bridge) endNames(event *irc.Event) { b.MMirc.names[channel] = nil } -func (b *Bridge) handleTopicWhoTime(event *irc.Event) bool { +func (b *Bridge) handleTopicWhoTime(event *irc.Event) { parts := strings.Split(event.Arguments[2], "!") - t_i, err := strconv.ParseInt(event.Arguments[3], 10, 64) + t, err := strconv.ParseInt(event.Arguments[3], 10, 64) if err != nil { flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) - return false } user := parts[0] if len(parts) > 1 { user += " [" + parts[1] + "]" } - flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t_i, 0)) - return true + flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0)) } func (b *Bridge) handleOther(event *irc.Event) { flog.irc.Debugf("%#v", event) - switch event.Code { - case ircm.RPL_WELCOME: - b.handleNewConnection(event) - case ircm.RPL_ENDOFNAMES: - b.endNames(event) - case ircm.RPL_NAMREPLY: - b.storeNames(event) - case ircm.RPL_ISUPPORT: - fallthrough - case ircm.RPL_LUSEROP: - fallthrough - case ircm.RPL_LUSERUNKNOWN: - fallthrough - case ircm.RPL_LUSERCHANNELS: - fallthrough - case ircm.RPL_MYINFO: - flog.irc.Infof("%s: %s", event.Code, strings.Join(event.Arguments[1:], " ")) - case ircm.RPL_YOURHOST: - fallthrough - case ircm.RPL_CREATED: - fallthrough - case ircm.RPL_STATSDLINE: - fallthrough - case ircm.RPL_LUSERCLIENT: - fallthrough - case ircm.RPL_LUSERME: - fallthrough - case ircm.RPL_LOCALUSERS: - fallthrough - case ircm.RPL_GLOBALUSERS: - fallthrough - case ircm.RPL_MOTD: - flog.irc.Infof("%s: %s", event.Code, event.Message()) - // flog.irc.Info(event.Message()) - case ircm.RPL_TOPIC: - flog.irc.Infof("%s: Topic for %s: %s", event.Code, event.Arguments[1], event.Message()) - case ircm.RPL_TOPICWHOTIME: - if !b.handleTopicWhoTime(event) { - break - } - case ircm.MODE: - flog.irc.Infof("%s: %s %s", event.Code, event.Arguments[1], event.Arguments[0]) - case ircm.JOIN: - fallthrough - case ircm.PING: - fallthrough - case ircm.PONG: - flog.irc.Infof("%s: %s", event.Code, event.Message()) - case ircm.RPL_ENDOFMOTD: - case ircm.RPL_MOTDSTART: - case ircm.ERR_NICKNAMEINUSE: - flog.irc.Warn(event.Message()) - case ircm.NOTICE: - b.handleNotice(event) - default: - flog.irc.Infof("UNKNOWN EVENT: %#v", event) - return - } } func (b *Bridge) Send(nick string, message string, channel string) error { @@ -327,7 +296,7 @@ func (b *Bridge) SendType(nick string, message string, channel string, mtype str message = nick + " " + message } } - if b.kind == "legacy" { + if b.kind == Legacy { matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} matterMessage.Channel = channel matterMessage.UserName = nick @@ -371,24 +340,34 @@ func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { } func (b *Bridge) handleMatter() { + flog.mm.Infof("Choosing Mattermost connection type %s", b.kind) mchan := make(chan *MMMessage) - if b.kind == "legacy" { + if b.kind == Legacy { go b.handleMatterHook(mchan) } else { go b.handleMatterClient(mchan) } + flog.mm.Info("Start listening for Mattermost messages") for message := range mchan { var username string + if b.ignoreMessage(message.Username, message.Text, "mattermost") { + continue + } username = message.Username + ": " if b.Config.IRC.RemoteNickFormat != "" { username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1) } else if b.Config.IRC.UseSlackCircumfix { username = "<" + message.Username + "> " } - cmd := strings.Fields(message.Text)[0] + cmds := strings.Fields(message.Text) + // empty message + if len(cmds) == 0 { + continue + } + cmd := cmds[0] switch cmd { case "!users": - flog.mm.Info("received !users from ", message.Username) + flog.mm.Info("Received !users from ", message.Username) b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel)) continue case "!gif": @@ -425,7 +404,7 @@ func (b *Bridge) getMMChannel(ircChannel string) string { } func (b *Bridge) getIRCChannel(channel string) string { - if b.kind == "legacy" { + if b.kind == Legacy { ircchannel := b.Config.IRC.Channel _, ok := b.Config.Token[channel] if ok { @@ -439,3 +418,17 @@ func (b *Bridge) getIRCChannel(channel string) string { } return ircchannel } + +func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool { + var ignoreNicks = b.mmIgnoreNicks + if protocol == "irc" { + ignoreNicks = b.ircIgnoreNicks + } + // should we discard messages ? + for _, entry := range ignoreNicks { + if nick == entry { + return true + } + } + return false +} diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/config.go b/vendor/github.com/42wim/matterbridge-plus/bridge/config.go index ca4eb225..f31db98d 100644 --- a/vendor/github.com/42wim/matterbridge-plus/bridge/config.go +++ b/vendor/github.com/42wim/matterbridge-plus/bridge/config.go @@ -19,6 +19,7 @@ type Config struct { NickServNick string NickServPassword string RemoteNickFormat string + IgnoreNicks string } Mattermost struct { URL string @@ -37,6 +38,8 @@ type Config struct { Login string Password string RemoteNickFormat *string + IgnoreNicks string + NoTLS bool } Token map[string]*struct { IRCChannel string diff --git a/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go b/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go index 13faf383..93128220 100644 --- a/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go +++ b/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go @@ -1,9 +1,12 @@ package matterclient import ( + "crypto/tls" "errors" log "github.com/Sirupsen/logrus" "net/http" + "net/http/cookiejar" + "net/url" "strings" "time" @@ -13,11 +16,12 @@ import ( ) type Credentials struct { - Login string - Team string - Pass string - Server string - NoTLS bool + Login string + Team string + Pass string + Server string + NoTLS bool + SkipTLSVerify bool } type Message struct { @@ -33,6 +37,8 @@ type MMClient struct { *Credentials Client *model.Client WsClient *websocket.Conn + WsQuit bool + WsAway bool Channels *model.ChannelList MoreChannels *model.ChannelList User *model.User @@ -60,6 +66,9 @@ func (m *MMClient) SetLogLevel(level string) { } func (m *MMClient) Login() error { + if m.WsQuit { + return nil + } b := &backoff.Backoff{ Min: time.Second, Max: 5 * time.Minute, @@ -73,12 +82,25 @@ func (m *MMClient) Login() error { } // login to mattermost m.Client = model.NewClient(uriScheme + m.Credentials.Server) + m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} var myinfo *model.Result var appErr *model.AppError var logmsg = "trying login" for { - m.log.Debugf(logmsg+" %s %s %s", m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) - myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) + m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) + if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { + m.log.Debugf(logmsg+" with ", model.SESSION_COOKIE_TOKEN) + token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=") + m.Client.HttpClient.Jar = m.createCookieJar(token[1]) + m.Client.MockSession(token[1]) + myinfo, appErr = m.Client.GetMe("") + if myinfo.Data.(*model.User) == nil { + m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass) + return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) + } + } else { + myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) + } if appErr != nil { d := b.Duration() m.log.Debug(appErr.DetailedError) @@ -89,7 +111,7 @@ func (m *MMClient) Login() error { } return errors.New(appErr.Message) } - m.log.Debug("LOGIN: %s, reconnecting in %s", appErr, d) + m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d) time.Sleep(d) logmsg = "retrying login" continue @@ -98,15 +120,16 @@ func (m *MMClient) Login() error { } // reset timer b.Reset() - m.User = myinfo.Data.(*model.User) - teamdata, _ := m.Client.GetAllTeamListings() - teams := teamdata.Data.(map[string]*model.Team) - for k, v := range teams { + initLoad, _ := m.Client.GetInitialLoad() + initData := initLoad.Data.(*model.InitialLoad) + m.User = initData.User + for _, v := range initData.Teams { + m.log.Debugf("trying %s (id: %s)", v.Name, v.Id) if v.Name == m.Credentials.Team { - m.Client.SetTeamId(k) + m.Client.SetTeamId(v.Id) m.Team = v - m.log.Debug("GetallTeamListings: found id ", k) + m.log.Debugf("GetallTeamListings: found id %s for team %s", v.Id, v.Name) break } } @@ -119,13 +142,14 @@ func (m *MMClient) Login() error { header := http.Header{} header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) - var WsClient *websocket.Conn + m.log.Debug("WsClient: making connection") var err error for { - WsClient, _, err = websocket.DefaultDialer.Dial(wsurl, header) + wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} + m.WsClient, _, err = wsDialer.Dial(wsurl, header) if err != nil { d := b.Duration() - log.Printf("WSS: %s, reconnecting in %s", err, d) + m.log.Debugf("WSS: %s, reconnecting in %s", err, d) time.Sleep(d) continue } @@ -133,8 +157,6 @@ func (m *MMClient) Login() error { } b.Reset() - m.WsClient = WsClient - // populating users m.UpdateUsers() @@ -147,12 +169,19 @@ func (m *MMClient) Login() error { func (m *MMClient) WsReceiver() { var rmsg model.Message for { + if m.WsQuit { + m.log.Debug("exiting WsReceiver") + return + } if err := m.WsClient.ReadJSON(&rmsg); err != nil { - log.Println("error:", err) + m.log.Error("error:", err) // reconnect m.Login() } - //log.Printf("WsReceiver: %#v", rmsg) + if rmsg.Action == "ping" { + m.handleWsPing() + continue + } msg := &Message{Raw: &rmsg, Team: m.Credentials.Team} m.parseMessage(msg) m.MessageChan <- msg @@ -160,6 +189,14 @@ func (m *MMClient) WsReceiver() { } +func (m *MMClient) handleWsPing() { + m.log.Debug("Ws PING") + if !m.WsQuit && !m.WsAway { + m.log.Debug("Ws PONG") + m.WsClient.WriteMessage(websocket.PongMessage, []byte{}) + } +} + func (m *MMClient) parseMessage(rmsg *Message) { switch rmsg.Raw.Action { case model.ACTION_POSTED: @@ -198,7 +235,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { } func (m *MMClient) UpdateUsers() error { - mmusers, _ := m.Client.GetProfiles(m.Client.GetTeamId(), "") + mmusers, _ := m.Client.GetProfilesForDirectMessageList(m.Team.Id) m.Users = mmusers.Data.(map[string]*model.User) return nil } @@ -294,29 +331,49 @@ func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { return res.Data.(*model.PostList) } +func (m *MMClient) GetPublicLink(filename string) string { + res, err := m.Client.GetPublicLink(filename) + if err != nil { + return "" + } + return res.Data.(string) +} + +func (m *MMClient) GetPublicLinks(filenames []string) []string { + var output []string + for _, f := range filenames { + res, err := m.Client.GetPublicLink(f) + if err != nil { + continue + } + output = append(output, res.Data.(string)) + } + return output +} + func (m *MMClient) UpdateChannelHeader(channelId string, header string) { data := make(map[string]string) data["channel_id"] = channelId data["channel_header"] = header - log.Printf("updating channelheader %#v, %#v", channelId, header) + m.log.Debugf("updating channelheader %#v, %#v", channelId, header) _, err := m.Client.UpdateChannelHeader(data) if err != nil { - log.Print(err) + log.Error(err) } } func (m *MMClient) UpdateLastViewed(channelId string) { - log.Printf("posting lastview %#v", channelId) + m.log.Debugf("posting lastview %#v", channelId) _, err := m.Client.UpdateLastViewedAt(channelId) if err != nil { - log.Print(err) + m.log.Error(err) } } func (m *MMClient) UsernamesInChannel(channelName string) []string { ceiRes, err := m.Client.GetChannelExtraInfo(m.GetChannelId(channelName), 5000, "") if err != nil { - log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) + m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) return []string{} } extra := ceiRes.Data.(*model.ChannelExtra) @@ -326,3 +383,59 @@ func (m *MMClient) UsernamesInChannel(channelName string) []string { } return result } + +func (m *MMClient) createCookieJar(token string) *cookiejar.Jar { + var cookies []*http.Cookie + jar, _ := cookiejar.New(nil) + firstCookie := &http.Cookie{ + Name: "MMAUTHTOKEN", + Value: token, + Path: "/", + Domain: m.Credentials.Server, + } + cookies = append(cookies, firstCookie) + cookieURL, _ := url.Parse("https://" + m.Credentials.Server) + jar.SetCookies(cookieURL, cookies) + return jar +} + +func (m *MMClient) GetOtherUserDM(channel string) *model.User { + m.UpdateUsers() + var rcvuser *model.User + if strings.Contains(channel, "__") { + rcvusers := strings.Split(channel, "__") + if rcvusers[0] != m.User.Id { + rcvuser = m.Users[rcvusers[0]] + } else { + rcvuser = m.Users[rcvusers[1]] + } + } + return rcvuser +} + +func (m *MMClient) SendDirectMessage(toUserId string, msg string) { + m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg) + var channel string + // We don't have a DM with this user yet. + if m.GetChannelId(toUserId+"__"+m.User.Id) == "" && m.GetChannelId(m.User.Id+"__"+toUserId) == "" { + // create DM channel + _, err := m.Client.CreateDirectChannel(toUserId) + if err != nil { + m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) + } + // update our channels + mmchannels, _ := m.Client.GetChannels("") + m.Channels = mmchannels.Data.(*model.ChannelList) + } + + // build the channel name + if toUserId > m.User.Id { + channel = m.User.Id + "__" + toUserId + } else { + channel = toUserId + "__" + m.User.Id + } + // build & send the message + msg = strings.Replace(msg, "\r", "", -1) + post := &model.Post{ChannelId: m.GetChannelId(channel), Message: msg} + m.Client.CreatePost(post) +} diff --git a/vendor/github.com/42wim/matterbridge/matterhook/LICENSE b/vendor/github.com/42wim/matterbridge/matterhook/LICENSE deleted file mode 100644 index 8f71f43f..00000000 --- a/vendor/github.com/42wim/matterbridge/matterhook/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/vendor/github.com/42wim/matterbridge/matterhook/matterhook.go b/vendor/github.com/42wim/matterbridge/matterhook/matterhook.go deleted file mode 100644 index 0b023d39..00000000 --- a/vendor/github.com/42wim/matterbridge/matterhook/matterhook.go +++ /dev/null @@ -1,154 +0,0 @@ -//Package matterhook provides interaction with mattermost incoming/outgoing webhooks -package matterhook - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "fmt" - "github.com/gorilla/schema" - "io" - "io/ioutil" - "log" - "net/http" - "strconv" -) - -// OMessage for mattermost incoming webhook. (send to mattermost) -type OMessage struct { - Channel string `json:"channel,omitempty"` - IconURL string `json:"icon_url,omitempty"` - IconEmoji string `json:"icon_emoji,omitempty"` - UserName string `json:"username,omitempty"` - Text string `json:"text"` - Attachments interface{} `json:"attachments,omitempty"` - Type string `json:"type,omitempty"` -} - -// IMessage for mattermost outgoing webhook. (received from mattermost) -type IMessage struct { - Token string `schema:"token"` - TeamID string `schema:"team_id"` - TeamDomain string `schema:"team_domain"` - ChannelID string `schema:"channel_id"` - ServiceID string `schema:"service_id"` - ChannelName string `schema:"channel_name"` - Timestamp string `schema:"timestamp"` - UserID string `schema:"user_id"` - UserName string `schema:"user_name"` - Text string `schema:"text"` - TriggerWord string `schema:"trigger_word"` -} - -// Client for Mattermost. -type Client struct { - Url string // URL for incoming webhooks on mattermost. - In chan IMessage - Out chan OMessage - httpclient *http.Client - Config -} - -// Config for client. -type Config struct { - Port int // Port to listen on. - BindAddress string // Address to listen on - Token string // Only allow this token from Mattermost. (Allow everything when empty) - InsecureSkipVerify bool // disable certificate checking - DisableServer bool // Do not start server for outgoing webhooks from Mattermost. -} - -// New Mattermost client. -func New(url string, config Config) *Client { - c := &Client{Url: url, In: make(chan IMessage), Out: make(chan OMessage), Config: config} - if c.Port == 0 { - c.Port = 9999 - } - c.BindAddress += ":" - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}, - } - c.httpclient = &http.Client{Transport: tr} - if !c.DisableServer { - go c.StartServer() - } - return c -} - -// StartServer starts a webserver listening for incoming mattermost POSTS. -func (c *Client) StartServer() { - mux := http.NewServeMux() - mux.Handle("/", c) - log.Printf("Listening on http://%v:%v...\n", c.BindAddress, c.Port) - if err := http.ListenAndServe((c.BindAddress + strconv.Itoa(c.Port)), mux); err != nil { - log.Fatal(err) - } -} - -// ServeHTTP implementation. -func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr) - http.NotFound(w, r) - return - } - msg := IMessage{} - err := r.ParseForm() - if err != nil { - log.Println(err) - http.NotFound(w, r) - return - } - defer r.Body.Close() - decoder := schema.NewDecoder() - err = decoder.Decode(&msg, r.PostForm) - if err != nil { - log.Println(err) - http.NotFound(w, r) - return - } - if msg.Token == "" { - log.Println("no token from " + r.RemoteAddr) - http.NotFound(w, r) - return - } - if c.Token != "" { - if msg.Token != c.Token { - log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) - http.NotFound(w, r) - return - } - } - c.In <- msg -} - -// Receive returns an incoming message from mattermost outgoing webhooks URL. -func (c *Client) Receive() IMessage { - for { - select { - case msg := <-c.In: - return msg - } - } -} - -// Send sends a msg to mattermost incoming webhooks URL. -func (c *Client) Send(msg OMessage) error { - buf, err := json.Marshal(msg) - if err != nil { - return err - } - resp, err := c.httpclient.Post(c.Url, "application/json", bytes.NewReader(buf)) - if err != nil { - return err - } - defer resp.Body.Close() - - // Read entire body to completion to re-use keep-alive connections. - io.Copy(ioutil.Discard, resp.Body) - - if resp.StatusCode != 200 { - return fmt.Errorf("unexpected status code: %d", resp.StatusCode) - } - return nil -} diff --git a/vendor/github.com/mattermost/platform/einterfaces/ldap.go b/vendor/github.com/mattermost/platform/einterfaces/ldap.go index 25d591ce..4f1b5611 100644 --- a/vendor/github.com/mattermost/platform/einterfaces/ldap.go +++ b/vendor/github.com/mattermost/platform/einterfaces/ldap.go @@ -13,6 +13,8 @@ type LdapInterface interface { CheckPassword(id string, password string) *model.AppError SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError ValidateFilter(filter string) *model.AppError + Syncronize() *model.AppError + StartLdapSyncJob() } var theLdapInterface LdapInterface diff --git a/vendor/github.com/mattermost/platform/model/client.go b/vendor/github.com/mattermost/platform/model/client.go index 152aaa70..e8ce21ab 100644 --- a/vendor/github.com/mattermost/platform/model/client.go +++ b/vendor/github.com/mattermost/platform/model/client.go @@ -28,6 +28,8 @@ const ( HEADER_AUTH = "Authorization" HEADER_REQUESTED_WITH = "X-Requested-With" HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" + STATUS = "status" + STATUS_OK = "OK" API_URL_SUFFIX_V1 = "/api/v1" API_URL_SUFFIX_V3 = "/api/v3" @@ -41,18 +43,28 @@ type Result struct { } type Client struct { - Url string // The location of the server like "http://localhost:8065" - ApiUrl string // The api location of the server like "http://localhost:8065/api/v3" - HttpClient *http.Client // The http client - AuthToken string - AuthType string - TeamId string + Url string // The location of the server like "http://localhost:8065" + ApiUrl string // The api location of the server like "http://localhost:8065/api/v3" + HttpClient *http.Client // The http client + AuthToken string + AuthType string + TeamId string + RequestId string + Etag string + ServerVersion string } // NewClient constructs a new client with convienence methods for talking to // the server. func NewClient(url string) *Client { - return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", "", ""} + return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", "", "", "", "", ""} +} + +func closeBody(r *http.Response) { + if r.Body != nil { + ioutil.ReadAll(r.Body) + r.Body.Close() + } } func (c *Client) SetOAuthToken(token string) { @@ -94,6 +106,10 @@ func (c *Client) GetChannelNameRoute(channelName string) string { return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName) } +func (c *Client) GetGeneralRoute() string { + return "/general" +} + func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) { rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data)) rq.Header.Set("Content-Type", contentType) @@ -101,6 +117,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro if rp, err := c.HttpClient.Do(rq); err != nil { return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) } else if rp.StatusCode >= 300 { + defer closeBody(rp) return nil, AppErrorFromJson(rp.Body) } else { return rp, nil @@ -117,6 +134,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) if rp, err := c.HttpClient.Do(rq); err != nil { return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) } else if rp.StatusCode >= 300 { + defer closeBody(rp) return nil, AppErrorFromJson(rp.Body) } else { return rp, nil @@ -139,6 +157,7 @@ func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, } else if rp.StatusCode == 304 { return rp, nil } else if rp.StatusCode >= 300 { + defer closeBody(rp) return rp, AppErrorFromJson(rp.Body) } else { return rp, nil @@ -155,6 +174,7 @@ func getCookie(name string, resp *http.Response) *http.Cookie { return nil } +// Must is a convenience function used for testing. func (c *Client) Must(result *Result, err *AppError) *Result { if err != nil { l4g.Close() @@ -165,6 +185,80 @@ func (c *Client) Must(result *Result, err *AppError) *Result { return result } +// CheckStatusOK is a convenience function for checking the return of Web Service +// call that return the a map of status=OK. +func (c *Client) CheckStatusOK(r *http.Response) bool { + m := MapFromJson(r.Body) + defer closeBody(r) + + if m != nil && m[STATUS] == STATUS_OK { + return true + } + + return false +} + +func (c *Client) fillInExtraProperties(r *http.Response) { + c.RequestId = r.Header.Get(HEADER_REQUEST_ID) + c.Etag = r.Header.Get(HEADER_ETAG_SERVER) + c.ServerVersion = r.Header.Get(HEADER_VERSION_ID) +} + +func (c *Client) clearExtraProperties() { + c.RequestId = "" + c.Etag = "" + c.ServerVersion = "" +} + +// General Routes Section + +// GetClientProperties returns properties needed by the client to show/hide +// certian features. It returns a map of strings. +func (c *Client) GetClientProperties() (map[string]string, *AppError) { + c.clearExtraProperties() + if r, err := c.DoApiGet(c.GetGeneralRoute()+"/client_props", "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + c.fillInExtraProperties(r) + return MapFromJson(r.Body), nil + } +} + +// LogClient is a convenience Web Service call so clients can log messages into +// the server-side logs. For example we typically log javascript error messages +// into the server-side. It returns true if the logging was successful. +func (c *Client) LogClient(message string) (bool, *AppError) { + c.clearExtraProperties() + m := make(map[string]string) + m["level"] = "ERROR" + m["message"] = message + + if r, err := c.DoApiPost(c.GetGeneralRoute()+"/log_client", MapToJson(m)); err != nil { + return false, err + } else { + defer closeBody(r) + c.fillInExtraProperties(r) + return c.CheckStatusOK(r), nil + } +} + +// GetPing returns a map of strings with server time, server version, and node Id. +// Systems that want to check on health status of the server should check the +// url /api/v3/ping for a 200 status response. +func (c *Client) GetPing() (map[string]string, *AppError) { + c.clearExtraProperties() + if r, err := c.DoApiGet(c.GetGeneralRoute()+"/ping", "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + c.fillInExtraProperties(r) + return MapFromJson(r.Body), nil + } +} + +// Team Routes Section + func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { m := make(map[string]string) m["email"] = email @@ -172,6 +266,7 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro if r, err := c.DoApiPost("/teams/signup", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -181,6 +276,7 @@ func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppErro if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), TeamSignupFromJson(r.Body)}, nil } @@ -190,6 +286,7 @@ func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil } @@ -199,7 +296,7 @@ func (c *Client) GetAllTeams() (*Result, *AppError) { if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { return nil, err } else { - + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil } @@ -209,7 +306,7 @@ func (c *Client) GetAllTeamListings() (*Result, *AppError) { if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { return nil, err } else { - + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil } @@ -225,7 +322,7 @@ func (c *Client) FindTeamByName(name string) (*Result, *AppError) { if body, _ := ioutil.ReadAll(r.Body); string(body) == "true" { val = true } - + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), val}, nil } @@ -237,6 +334,7 @@ func (c *Client) AddUserToTeam(userId string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/add_user_to_team", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -250,6 +348,7 @@ func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Re if r, err := c.DoApiPost("/teams/add_user_to_team_from_invite", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil } @@ -259,6 +358,7 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), InvitesFromJson(r.Body)}, nil } @@ -268,6 +368,7 @@ func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -277,6 +378,7 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -289,6 +391,7 @@ func (c *Client) CreateUserWithInvite(user *User, hash string, data string, invi if r, err := c.DoApiPost(url, user.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -298,6 +401,7 @@ func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Re if r, err := c.DoApiPost("/users/create?d="+url.QueryEscape(data)+"&h="+hash, user.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -307,6 +411,7 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -316,6 +421,7 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/me", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -325,6 +431,7 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil } @@ -334,6 +441,7 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil } @@ -343,6 +451,7 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil } @@ -390,6 +499,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) { NewLocAppError("/users/login", "model.client.login.app_error", nil, "") } + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -403,6 +513,7 @@ func (c *Client) Logout() (*Result, *AppError) { c.AuthType = HEADER_BEARER c.TeamId = "" + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -415,6 +526,7 @@ func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/mfa", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -424,6 +536,7 @@ func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), r.Body}, nil } @@ -437,6 +550,7 @@ func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/update_mfa", StringInterfaceToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -449,6 +563,7 @@ func (c *Client) AdminResetMfa(userId string) (*Result, *AppError) { if r, err := c.DoApiPost("/admin/reset_mfa", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -461,6 +576,7 @@ func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/revoke_session", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -470,6 +586,7 @@ func (c *Client) GetSessions(id string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/"+id+"/sessions", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), SessionsFromJson(r.Body)}, nil } @@ -479,6 +596,7 @@ func (c *Client) EmailToOAuth(m map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/claim/email_to_sso", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -488,6 +606,7 @@ func (c *Client) OAuthToEmail(m map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/claim/oauth_to_email", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -497,6 +616,7 @@ func (c *Client) LDAPToEmail(m map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -506,6 +626,7 @@ func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -519,6 +640,7 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", MapToJson(m)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), CommandResponseFromJson(r.Body)}, nil } @@ -528,6 +650,7 @@ func (c *Client) ListCommands() (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil } @@ -537,6 +660,7 @@ func (c *Client) ListTeamCommands() (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list_team_commands", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil } @@ -546,6 +670,7 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/create", cmd.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil } @@ -555,6 +680,7 @@ func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil } @@ -564,6 +690,7 @@ func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/delete", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -573,6 +700,7 @@ func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil } @@ -582,6 +710,7 @@ func (c *Client) GetLogs() (*Result, *AppError) { if r, err := c.DoApiGet("/admin/logs", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil } @@ -591,42 +720,64 @@ func (c *Client) GetAllAudits() (*Result, *AppError) { if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil } } -func (c *Client) GetClientProperties() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/client_props", "", ""); err != nil { - return nil, err - } else { - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - func (c *Client) GetConfig() (*Result, *AppError) { if r, err := c.DoApiGet("/admin/config", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ConfigFromJson(r.Body)}, nil } } +// ReloadConfig will reload the config.json file from disk. Properties +// requiring a server restart will still need a server restart. You must +// have the system admin role to call this method. It will return status=OK +// if it's successfully reloaded the config file, otherwise check the returned error. +func (c *Client) ReloadConfig() (bool, *AppError) { + c.clearExtraProperties() + if r, err := c.DoApiGet("/admin/reload_config", "", ""); err != nil { + return false, err + } else { + c.fillInExtraProperties(r) + return c.CheckStatusOK(r), nil + } +} + func (c *Client) SaveConfig(config *Config) (*Result, *AppError) { if r, err := c.DoApiPost("/admin/save_config", config.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } } +// RecycleDatabaseConnection will attempt to recycle the database connections. +// You must have the system admin role to call this method. It will return status=OK +// if it's successfully recycled the connections, otherwise check the returned error. +func (c *Client) RecycleDatabaseConnection() (bool, *AppError) { + c.clearExtraProperties() + if r, err := c.DoApiGet("/admin/recycle_db_conn", "", ""); err != nil { + return false, err + } else { + c.fillInExtraProperties(r) + return c.CheckStatusOK(r), nil + } +} + func (c *Client) TestEmail(config *Config) (*Result, *AppError) { if r, err := c.DoApiPost("/admin/test_email", config.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -636,6 +787,7 @@ func (c *Client) GetComplianceReports() (*Result, *AppError) { if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), CompliancesFromJson(r.Body)}, nil } @@ -645,6 +797,7 @@ func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) { if r, err := c.DoApiPost("/admin/save_compliance_report", job.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ComplianceFromJson(r.Body)}, nil } @@ -661,8 +814,10 @@ func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) { if rp, err := c.HttpClient.Do(rq); err != nil { return nil, NewLocAppError("/admin/download_compliance_report", "model.client.connecting.app_error", nil, err.Error()) } else if rp.StatusCode >= 300 { + defer rp.Body.Close() return nil, AppErrorFromJson(rp.Body) } else { + defer closeBody(rp) return &Result{rp.Header.Get(HEADER_REQUEST_ID), rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil } @@ -672,6 +827,7 @@ func (c *Client) GetTeamAnalytics(teamId, name string) (*Result, *AppError) { if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil } @@ -681,6 +837,7 @@ func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) { if r, err := c.DoApiGet("/admin/analytics/"+name, "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil } @@ -690,6 +847,7 @@ func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create", channel.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil } @@ -701,6 +859,7 @@ func (c *Client) CreateDirectChannel(userId string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_direct", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil } @@ -710,6 +869,7 @@ func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update", channel.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil } @@ -719,6 +879,7 @@ func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_header", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil } @@ -728,6 +889,7 @@ func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppErro if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_purpose", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil } @@ -737,6 +899,7 @@ func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_notify_props", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -746,6 +909,7 @@ func (c *Client) GetChannels(etag string) (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil } @@ -755,6 +919,7 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) { if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelDataFromJson(r.Body)}, nil } @@ -764,6 +929,7 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/more", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil } @@ -773,6 +939,7 @@ func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelCountsFromJson(r.Body)}, nil } @@ -849,6 +1016,7 @@ func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (* if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil } @@ -858,6 +1026,7 @@ func (c *Client) CreatePost(post *Post) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/create", post.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil } @@ -867,6 +1036,7 @@ func (c *Client) UpdatePost(post *Post) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/update", post.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil } @@ -876,6 +1046,7 @@ func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/page/%v/%v", offset, limit), "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil } @@ -885,6 +1056,7 @@ func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/since/%v", time), "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil } @@ -894,6 +1066,7 @@ func (c *Client) GetPostsBefore(channelId string, postid string, offset int, lim if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/before/%v/%v", postid, offset, limit), "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil } @@ -903,6 +1076,7 @@ func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limi if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/posts/%v/after/%v/%v", postid, offset, limit), "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil } @@ -912,6 +1086,7 @@ func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get", postId), "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil } @@ -921,6 +1096,7 @@ func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -933,6 +1109,7 @@ func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError) if r, err := c.DoApiPost(c.GetTeamRoute()+"/posts/search", StringInterfaceToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil } @@ -959,6 +1136,7 @@ func (c *Client) uploadFile(url string, data []byte, contentType string) (*Resul } else if rp.StatusCode >= 300 { return nil, AppErrorFromJson(rp.Body) } else { + defer closeBody(rp) return &Result{rp.Header.Get(HEADER_REQUEST_ID), rp.Header.Get(HEADER_ETAG_SERVER), FileUploadResponseFromJson(rp.Body)}, nil } @@ -981,6 +1159,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) { } else if rp.StatusCode >= 300 { return nil, AppErrorFromJson(rp.Body) } else { + defer closeBody(rp) return &Result{rp.Header.Get(HEADER_REQUEST_ID), rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil } @@ -999,6 +1178,7 @@ func (c *Client) GetFileInfo(url string) (*Result, *AppError) { } else if rp.StatusCode >= 300 { return nil, AppErrorFromJson(rp.Body) } else { + defer closeBody(rp) return &Result{rp.Header.Get(HEADER_REQUEST_ID), rp.Header.Get(HEADER_ETAG_SERVER), FileInfoFromJson(rp.Body)}, nil } @@ -1008,6 +1188,7 @@ func (c *Client) GetPublicLink(filename string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/files/get_public_link", MapToJson(map[string]string{"filename": filename})); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), StringFromJson(r.Body)}, nil } @@ -1017,6 +1198,7 @@ func (c *Client) UpdateUser(user *User) (*Result, *AppError) { if r, err := c.DoApiPost("/users/update", user.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -1026,6 +1208,7 @@ func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/update_roles", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1037,6 +1220,7 @@ func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/attach_device", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -1049,6 +1233,7 @@ func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) { if r, err := c.DoApiPost("/users/update_active", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -1058,6 +1243,7 @@ func (c *Client) UpdateUserNotify(data map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/update_notify", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil } @@ -1072,6 +1258,7 @@ func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string) if r, err := c.DoApiPost("/users/newpassword", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1083,6 +1270,7 @@ func (c *Client) SendPasswordReset(email string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/send_password_reset", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1095,6 +1283,7 @@ func (c *Client) ResetPassword(code, newPassword string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/reset_password", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1107,6 +1296,7 @@ func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppEr if r, err := c.DoApiPost("/admin/reset_password", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1116,6 +1306,7 @@ func (c *Client) GetStatuses(data []string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1125,6 +1316,7 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil } @@ -1134,6 +1326,7 @@ func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil } @@ -1143,6 +1336,7 @@ func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil } @@ -1152,6 +1346,7 @@ func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (* if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1161,6 +1356,7 @@ func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { if r, err := c.DoApiPost("/oauth/access_token", data.Encode()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), AccessResponseFromJson(r.Body)}, nil } @@ -1170,6 +1366,7 @@ func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppErro if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/create", hook.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookFromJson(r.Body)}, nil } @@ -1190,6 +1387,7 @@ func (c *Client) DeleteIncomingWebhook(id string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/delete", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1199,6 +1397,7 @@ func (c *Client) ListIncomingWebhooks() (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/incoming/list", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookListFromJson(r.Body)}, nil } @@ -1208,6 +1407,7 @@ func (c *Client) GetAllPreferences() (*Result, *AppError) { if r, err := c.DoApiGet("/preferences/", "", ""); err != nil { return nil, err } else { + defer closeBody(r) preferences, _ := PreferencesFromJson(r.Body) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil } @@ -1226,6 +1426,7 @@ func (c *Client) GetPreference(category string, name string) (*Result, *AppError if r, err := c.DoApiGet("/preferences/"+category+"/"+name, "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PreferenceFromJson(r.Body)}, nil } } @@ -1234,6 +1435,7 @@ func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) { if r, err := c.DoApiGet("/preferences/"+category, "", ""); err != nil { return nil, err } else { + defer closeBody(r) preferences, _ := PreferencesFromJson(r.Body) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil } @@ -1243,6 +1445,7 @@ func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppErro if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil } @@ -1254,6 +1457,7 @@ func (c *Client) DeleteOutgoingWebhook(id string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/delete", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1263,6 +1467,7 @@ func (c *Client) ListOutgoingWebhooks() (*Result, *AppError) { if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/outgoing/list", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookListFromJson(r.Body)}, nil } @@ -1274,6 +1479,7 @@ func (c *Client) RegenOutgoingWebhookToken(id string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/regen_token", MapToJson(data)); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil } @@ -1288,6 +1494,7 @@ func (c *Client) GetClientLicenceConfig(etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/license/client_config", "", etag); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } @@ -1297,6 +1504,7 @@ func (c *Client) GetInitialLoad() (*Result, *AppError) { if r, err := c.DoApiGet("/users/initial_load", "", ""); err != nil { return nil, err } else { + defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), InitialLoadFromJson(r.Body)}, nil } diff --git a/vendor/github.com/mattermost/platform/model/command.go b/vendor/github.com/mattermost/platform/model/command.go index 4d5f7ace..decb647b 100644 --- a/vendor/github.com/mattermost/platform/model/command.go +++ b/vendor/github.com/mattermost/platform/model/command.go @@ -6,11 +6,14 @@ package model import ( "encoding/json" "io" + "strings" ) const ( COMMAND_METHOD_POST = "P" COMMAND_METHOD_GET = "G" + MIN_TRIGGER_LENGTH = 1 + MAX_TRIGGER_LENGTH = 128 ) type Command struct { @@ -99,7 +102,7 @@ func (o *Command) IsValid() *AppError { return NewLocAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "") } - if len(o.Trigger) == 0 || len(o.Trigger) > 128 { + if len(o.Trigger) < MIN_TRIGGER_LENGTH || len(o.Trigger) > MAX_TRIGGER_LENGTH || strings.Index(o.Trigger, "/") == 0 || strings.Contains(o.Trigger, " ") { return NewLocAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "") } diff --git a/vendor/github.com/mattermost/platform/model/config.go b/vendor/github.com/mattermost/platform/model/config.go index eb25948f..55606ee8 100644 --- a/vendor/github.com/mattermost/platform/model/config.go +++ b/vendor/github.com/mattermost/platform/model/config.go @@ -6,6 +6,7 @@ package model import ( "encoding/json" "io" + "strings" ) const ( @@ -35,6 +36,15 @@ const ( FAKE_SETTING = "********************************" ) +// should match the values in webapp/i18n/i18n.jsx +var LOCALES = []string{ + "en", + "es", + "fr", + "ja", + "pt-BR", +} + type ServiceSettings struct { ListenAddress string MaximumLoginAttempts int @@ -92,6 +102,7 @@ type LogSettings struct { } type FileSettings struct { + MaxFileSize *int64 DriverName string Directory string EnablePublicLink bool @@ -189,6 +200,9 @@ type LdapSettings struct { NicknameAttribute *string IdAttribute *string + // Syncronization + SyncIntervalMinutes *int + // Advanced SkipCertificateVerification *bool QueryTimeout *int @@ -203,20 +217,27 @@ type ComplianceSettings struct { EnableDaily *bool } +type LocalizationSettings struct { + DefaultServerLocale *string + DefaultClientLocale *string + AvailableLocales *string +} + type Config struct { - ServiceSettings ServiceSettings - TeamSettings TeamSettings - SqlSettings SqlSettings - LogSettings LogSettings - FileSettings FileSettings - EmailSettings EmailSettings - RateLimitSettings RateLimitSettings - PrivacySettings PrivacySettings - SupportSettings SupportSettings - GitLabSettings SSOSettings - GoogleSettings SSOSettings - LdapSettings LdapSettings - ComplianceSettings ComplianceSettings + ServiceSettings ServiceSettings + TeamSettings TeamSettings + SqlSettings SqlSettings + LogSettings LogSettings + FileSettings FileSettings + EmailSettings EmailSettings + RateLimitSettings RateLimitSettings + PrivacySettings PrivacySettings + SupportSettings SupportSettings + GitLabSettings SSOSettings + GoogleSettings SSOSettings + LdapSettings LdapSettings + ComplianceSettings ComplianceSettings + LocalizationSettings LocalizationSettings } func (o *Config) ToJson() string { @@ -256,6 +277,11 @@ func (o *Config) SetDefaults() { o.SqlSettings.AtRestEncryptKey = NewRandomString(32) } + if o.FileSettings.MaxFileSize == nil { + o.FileSettings.MaxFileSize = new(int64) + *o.FileSettings.MaxFileSize = 52428800 // 50 MB + } + if len(o.FileSettings.PublicLinkSalt) == 0 { o.FileSettings.PublicLinkSalt = NewRandomString(32) } @@ -403,26 +429,86 @@ func (o *Config) SetDefaults() { *o.SupportSettings.SupportEmail = "feedback@mattermost.com" } - if o.LdapSettings.LdapPort == nil { - o.LdapSettings.LdapPort = new(int) - *o.LdapSettings.LdapPort = 389 - } - - if o.LdapSettings.QueryTimeout == nil { - o.LdapSettings.QueryTimeout = new(int) - *o.LdapSettings.QueryTimeout = 60 - } - if o.LdapSettings.Enable == nil { o.LdapSettings.Enable = new(bool) *o.LdapSettings.Enable = false } + if o.LdapSettings.LdapServer == nil { + o.LdapSettings.LdapServer = new(string) + *o.LdapSettings.LdapServer = "" + } + + if o.LdapSettings.LdapPort == nil { + o.LdapSettings.LdapPort = new(int) + *o.LdapSettings.LdapPort = 389 + } + + if o.LdapSettings.ConnectionSecurity == nil { + o.LdapSettings.ConnectionSecurity = new(string) + *o.LdapSettings.ConnectionSecurity = "" + } + + if o.LdapSettings.BaseDN == nil { + o.LdapSettings.BaseDN = new(string) + *o.LdapSettings.BaseDN = "" + } + + if o.LdapSettings.BindUsername == nil { + o.LdapSettings.BindUsername = new(string) + *o.LdapSettings.BindUsername = "" + } + + if o.LdapSettings.BindPassword == nil { + o.LdapSettings.BindPassword = new(string) + *o.LdapSettings.BindPassword = "" + } + if o.LdapSettings.UserFilter == nil { o.LdapSettings.UserFilter = new(string) *o.LdapSettings.UserFilter = "" } + if o.LdapSettings.FirstNameAttribute == nil { + o.LdapSettings.FirstNameAttribute = new(string) + *o.LdapSettings.FirstNameAttribute = "" + } + + if o.LdapSettings.LastNameAttribute == nil { + o.LdapSettings.LastNameAttribute = new(string) + *o.LdapSettings.LastNameAttribute = "" + } + + if o.LdapSettings.EmailAttribute == nil { + o.LdapSettings.EmailAttribute = new(string) + *o.LdapSettings.EmailAttribute = "" + } + + if o.LdapSettings.NicknameAttribute == nil { + o.LdapSettings.NicknameAttribute = new(string) + *o.LdapSettings.NicknameAttribute = "" + } + + if o.LdapSettings.IdAttribute == nil { + o.LdapSettings.IdAttribute = new(string) + *o.LdapSettings.IdAttribute = "" + } + + if o.LdapSettings.SyncIntervalMinutes == nil { + o.LdapSettings.SyncIntervalMinutes = new(int) + *o.LdapSettings.SyncIntervalMinutes = 60 + } + + if o.LdapSettings.SkipCertificateVerification == nil { + o.LdapSettings.SkipCertificateVerification = new(bool) + *o.LdapSettings.SkipCertificateVerification = false + } + + if o.LdapSettings.QueryTimeout == nil { + o.LdapSettings.QueryTimeout = new(int) + *o.LdapSettings.QueryTimeout = 60 + } + if o.LdapSettings.LoginFieldName == nil { o.LdapSettings.LoginFieldName = new(string) *o.LdapSettings.LoginFieldName = "" @@ -493,19 +579,19 @@ func (o *Config) SetDefaults() { *o.ComplianceSettings.EnableDaily = false } - if o.LdapSettings.ConnectionSecurity == nil { - o.LdapSettings.ConnectionSecurity = new(string) - *o.LdapSettings.ConnectionSecurity = "" + if o.LocalizationSettings.DefaultServerLocale == nil { + o.LocalizationSettings.DefaultServerLocale = new(string) + *o.LocalizationSettings.DefaultServerLocale = DEFAULT_LOCALE } - if o.LdapSettings.SkipCertificateVerification == nil { - o.LdapSettings.SkipCertificateVerification = new(bool) - *o.LdapSettings.SkipCertificateVerification = false + if o.LocalizationSettings.DefaultClientLocale == nil { + o.LocalizationSettings.DefaultClientLocale = new(string) + *o.LocalizationSettings.DefaultClientLocale = DEFAULT_LOCALE } - if o.LdapSettings.NicknameAttribute == nil { - o.LdapSettings.NicknameAttribute = new(string) - *o.LdapSettings.NicknameAttribute = "" + if o.LocalizationSettings.AvailableLocales == nil { + o.LocalizationSettings.AvailableLocales = new(string) + *o.LocalizationSettings.AvailableLocales = strings.Join(LOCALES, ",") } } @@ -547,6 +633,10 @@ func (o *Config) IsValid() *AppError { return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "") } + if *o.FileSettings.MaxFileSize <= 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.max_file_size.app_error", nil, "") + } + if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) { return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "") } @@ -603,6 +693,10 @@ func (o *Config) IsValid() *AppError { return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "") } + if *o.LdapSettings.SyncIntervalMinutes <= 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_sync_interval.app_error", nil, "") + } + return nil } @@ -615,7 +709,7 @@ func (o *Config) GetSanitizeOptions() map[string]bool { } func (o *Config) Sanitize() { - if &o.LdapSettings != nil && len(*o.LdapSettings.BindPassword) > 0 { + if o.LdapSettings.BindPassword != nil && len(*o.LdapSettings.BindPassword) > 0 { *o.LdapSettings.BindPassword = FAKE_SETTING } diff --git a/vendor/github.com/mattermost/platform/model/file.go b/vendor/github.com/mattermost/platform/model/file.go index b7806b3b..fa98a3b3 100644 --- a/vendor/github.com/mattermost/platform/model/file.go +++ b/vendor/github.com/mattermost/platform/model/file.go @@ -8,10 +8,6 @@ import ( "io" ) -const ( - MAX_FILE_SIZE = 50000000 // 50 MB -) - var ( IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"} IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"} diff --git a/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go b/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go index fc70dd93..e189ee68 100644 --- a/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go +++ b/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go @@ -47,7 +47,8 @@ func userFromGitLabUser(glu *GitLabUser) *model.User { } strings.TrimSpace(user.Email) user.Email = glu.Email - *user.AuthData = strconv.FormatInt(glu.Id, 10) + userId := strconv.FormatInt(glu.Id, 10) + user.AuthData = &userId user.AuthService = model.USER_AUTH_SERVICE_GITLAB return user diff --git a/vendor/github.com/mattermost/platform/model/incoming_webhook.go b/vendor/github.com/mattermost/platform/model/incoming_webhook.go index 0763b443..c567edfd 100644 --- a/vendor/github.com/mattermost/platform/model/incoming_webhook.go +++ b/vendor/github.com/mattermost/platform/model/incoming_webhook.go @@ -4,13 +4,15 @@ package model import ( + "bytes" "encoding/json" "io" + "regexp" + "strings" ) const ( DEFAULT_WEBHOOK_USERNAME = "webhook" - DEFAULT_WEBHOOK_ICON = "/static/images/webhook_icon.jpg" ) type IncomingWebhook struct { @@ -125,13 +127,136 @@ func (o *IncomingWebhook) PreUpdate() { o.UpdateAt = GetMillis() } -func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { - decoder := json.NewDecoder(data) +// escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice. +// Context: +// JSON strings are not supposed to contain control characters such as \n, \t, +// ... but some incoming webhooks might still send invalid JSON and we want to +// try to handle that. An example invalid JSON string from an incoming webhook +// might look like this (strings for both "text" and "fallback" attributes are +// invalid JSON strings because they contain unescaped newlines and tabs): +// `{ +// "text": "this is a test +// that contains a newline and tabs", +// "attachments": [ +// { +// "fallback": "Required plain-text summary of the attachment +// that contains a newline and tabs", +// "color": "#36a64f", +// ... +// "text": "Optional text that appears within the attachment +// that contains a newline and tabs", +// ... +// "thumb_url": "http://example.com/path/to/thumb.png" +// } +// ] +// }` +// This function will search for `"key": "value"` pairs, and escape \n, \t +// from the value. +func escapeControlCharsFromPayload(by []byte) []byte { + // we'll search for `"text": "..."` or `"fallback": "..."`, ... + keys := "text|fallback|pretext|author_name|title|value" + + // the regexp reads like this: + // (?s): this flag let . match \n (default is false) + // "(keys)": we search for the keys defined above + // \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs + // ": a double-quote + // (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote + // ": a double-quote + r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"` + re := regexp.MustCompile(r) + + // the function that will escape \n and \t on the regexp matches + repl := func(b []byte) []byte { + if bytes.Contains(b, []byte("\n")) { + b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1) + } + if bytes.Contains(b, []byte("\t")) { + b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1) + } + + return b + } + + return re.ReplaceAllFunc(by, repl) +} + +func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) { + decoder := json.NewDecoder(bytes.NewReader(by)) var o IncomingWebhookRequest err := decoder.Decode(&o) if err == nil { - return &o + return &o, nil } else { - return nil + return nil, err } } + +// To mention @channel via a webhook in Slack, the message should contain +// , as explained at the bottom of this article: +// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements +func expandAnnouncement(text string) string { + c1 := "" + c2 := "@channel" + if strings.Contains(text, c1) { + return strings.Replace(text, c1, c2, -1) + } + return text +} + +// Expand announcements in incoming webhooks from Slack. Those announcements +// can be found in the text attribute, or in the pretext, text, title and value +// attributes of the attachment structure. The Slack attachment structure is +// documented here: https://api.slack.com/docs/attachments +func expandAnnouncements(i *IncomingWebhookRequest) { + i.Text = expandAnnouncement(i.Text) + + if i.Attachments != nil { + attachments := i.Attachments.([]interface{}) + for _, attachment := range attachments { + a := attachment.(map[string]interface{}) + + if a["pretext"] != nil { + a["pretext"] = expandAnnouncement(a["pretext"].(string)) + } + + if a["text"] != nil { + a["text"] = expandAnnouncement(a["text"].(string)) + } + + if a["title"] != nil { + a["title"] = expandAnnouncement(a["title"].(string)) + } + + if a["fields"] != nil { + fields := a["fields"].([]interface{}) + for _, field := range fields { + f := field.(map[string]interface{}) + if f["value"] != nil { + f["value"] = expandAnnouncement(f["value"].(string)) + } + } + } + } + } +} + +func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { + buf := new(bytes.Buffer) + buf.ReadFrom(data) + by := buf.Bytes() + + // Try to decode the JSON data. Only if it fails, try to escape control + // characters from the strings contained in the JSON data. + o, err := decodeIncomingWebhookRequest(by) + if err != nil { + o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by)) + if err != nil { + return nil + } + } + + expandAnnouncements(o) + + return o +} diff --git a/vendor/github.com/mattermost/platform/model/job.go b/vendor/github.com/mattermost/platform/model/job.go new file mode 100644 index 00000000..bcae7a83 --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/job.go @@ -0,0 +1,91 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "fmt" + "time" +) + +type TaskFunc func() + +type ScheduledTask struct { + Name string `json:"name"` + Interval time.Duration `json:"interval"` + Recurring bool `json:"recurring"` + function TaskFunc `json:",omitempty"` + timer *time.Timer `json:",omitempty"` +} + +var tasks = make(map[string]*ScheduledTask) + +func addTask(task *ScheduledTask) { + tasks[task.Name] = task +} + +func removeTaskByName(name string) { + delete(tasks, name) +} + +func getTaskByName(name string) *ScheduledTask { + return tasks[name] +} + +func GetAllTasks() *map[string]*ScheduledTask { + return &tasks +} + +func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask { + task := &ScheduledTask{ + Name: name, + Interval: timeToExecution, + Recurring: false, + function: function, + } + + taskRunner := func() { + go task.function() + removeTaskByName(task.Name) + } + + task.timer = time.AfterFunc(timeToExecution, taskRunner) + + addTask(task) + + return task +} + +func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask { + task := &ScheduledTask{ + Name: name, + Interval: interval, + Recurring: true, + function: function, + } + + taskRecurer := func() { + go task.function() + task.timer.Reset(task.Interval) + } + + task.timer = time.AfterFunc(interval, taskRecurer) + + addTask(task) + + return task +} + +func (task *ScheduledTask) Cancel() { + task.timer.Stop() + removeTaskByName(task.Name) +} + +func (task *ScheduledTask) String() string { + return fmt.Sprintf( + "%s\nInterval: %s\nRecurring: %t\n", + task.Name, + task.Interval.String(), + task.Recurring, + ) +} diff --git a/vendor/github.com/mattermost/platform/model/outgoing_webhook.go b/vendor/github.com/mattermost/platform/model/outgoing_webhook.go index ef1807e7..ee7a32f1 100644 --- a/vendor/github.com/mattermost/platform/model/outgoing_webhook.go +++ b/vendor/github.com/mattermost/platform/model/outgoing_webhook.go @@ -7,6 +7,8 @@ import ( "encoding/json" "fmt" "io" + "net/url" + "strconv" ) type OutgoingWebhook struct { @@ -22,6 +24,47 @@ type OutgoingWebhook struct { CallbackURLs StringArray `json:"callback_urls"` DisplayName string `json:"display_name"` Description string `json:"description"` + ContentType string `json:"content_type"` +} + +type OutgoingWebhookPayload struct { + Token string `json:"token"` + TeamId string `json:"team_id"` + TeamDomain string `json:"team_domain"` + ChannelId string `json:"channel_id"` + ChannelName string `json:"channel_name"` + Timestamp int64 `json:"timestamp"` + UserId string `json:"user_id"` + UserName string `json:"user_name"` + PostId string `json:"post_id"` + Text string `json:"text"` + TriggerWord string `json:"trigger_word"` +} + +func (o *OutgoingWebhookPayload) ToJSON() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func (o *OutgoingWebhookPayload) ToFormValues() string { + v := url.Values{} + v.Set("token", o.Token) + v.Set("team_id", o.TeamId) + v.Set("team_domain", o.TeamDomain) + v.Set("channel_id", o.ChannelId) + v.Set("channel_name", o.ChannelName) + v.Set("timestamp", strconv.FormatInt(o.Timestamp/1000, 10)) + v.Set("user_id", o.UserId) + v.Set("user_name", o.UserName) + v.Set("post_id", o.PostId) + v.Set("text", o.Text) + v.Set("trigger_word", o.TriggerWord) + + return v.Encode() } func (o *OutgoingWebhook) ToJson() string { @@ -124,6 +167,10 @@ func (o *OutgoingWebhook) IsValid() *AppError { return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "") } + if len(o.ContentType) > 128 { + return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") + } + return nil } diff --git a/vendor/github.com/mattermost/platform/model/preference.go b/vendor/github.com/mattermost/platform/model/preference.go index b2ec9310..22858e04 100644 --- a/vendor/github.com/mattermost/platform/model/preference.go +++ b/vendor/github.com/mattermost/platform/model/preference.go @@ -14,6 +14,9 @@ const ( PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step" PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings" + PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" + PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews" + PREFERENCE_CATEGORY_LAST = "last" PREFERENCE_NAME_LAST_CHANNEL = "channel" ) diff --git a/vendor/github.com/mattermost/platform/model/search_params.go b/vendor/github.com/mattermost/platform/model/search_params.go index 250c8e1f..1772c902 100644 --- a/vendor/github.com/mattermost/platform/model/search_params.go +++ b/vendor/github.com/mattermost/platform/model/search_params.go @@ -4,9 +4,13 @@ package model import ( + "regexp" "strings" ) +var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`) +var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`) + type SearchParams struct { Terms string IsHashtag bool @@ -91,8 +95,8 @@ func parseSearchFlags(input []string) ([]string, [][2]string) { if !isFlag { // trim off surrounding punctuation (note that we leave trailing asterisks to allow wildcards) - word = puncStart.ReplaceAllString(word, "") - word = puncEndWildcard.ReplaceAllString(word, "") + word = searchTermPuncStart.ReplaceAllString(word, "") + word = searchTermPuncEnd.ReplaceAllString(word, "") // and remove extra pound #s word = hashtagStart.ReplaceAllString(word, "#") diff --git a/vendor/github.com/mattermost/platform/model/user.go b/vendor/github.com/mattermost/platform/model/user.go index 15c28140..ab076894 100644 --- a/vendor/github.com/mattermost/platform/model/user.go +++ b/vendor/github.com/mattermost/platform/model/user.go @@ -136,7 +136,6 @@ func (u *User) PreSave() { u.Username = strings.ToLower(u.Username) u.Email = strings.ToLower(u.Email) - u.Locale = strings.ToLower(u.Locale) u.CreateAt = GetMillis() u.UpdateAt = u.CreateAt @@ -166,7 +165,6 @@ func (u *User) PreSave() { func (u *User) PreUpdate() { u.Username = strings.ToLower(u.Username) u.Email = strings.ToLower(u.Email) - u.Locale = strings.ToLower(u.Locale) u.UpdateAt = GetMillis() if u.AuthData != nil && *u.AuthData == "" { @@ -186,11 +184,27 @@ func (u *User) PreUpdate() { } u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",") } + + if u.ThemeProps != nil { + colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) + + // blank out any invalid theme values + for name, value := range u.ThemeProps { + if name == "image" || name == "type" || name == "codeTheme" { + continue + } + + if !colorPattern.MatchString(value) { + u.ThemeProps[name] = "#ffffff" + } + } + } } func (u *User) SetDefaultNotifications() { u.NotifyProps = make(map[string]string) u.NotifyProps["email"] = "true" + u.NotifyProps["push"] = USER_NOTIFY_MENTION u.NotifyProps["desktop"] = USER_NOTIFY_ALL u.NotifyProps["desktop_sound"] = "true" u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username diff --git a/vendor/github.com/mattermost/platform/model/utils.go b/vendor/github.com/mattermost/platform/model/utils.go index 443a34bc..27093c09 100644 --- a/vendor/github.com/mattermost/platform/model/utils.go +++ b/vendor/github.com/mattermost/platform/model/utils.go @@ -315,10 +315,9 @@ func Etag(parts ...interface{}) string { } var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`) -var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\<>\-+=%^*|]+`) +var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) var hashtagStart = regexp.MustCompile(`^#{2,}`) -var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^*|]+$`) -var puncEndWildcard = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^|]+$`) +var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) func ParseHashtags(text string) (string, string) { words := strings.Fields(text) diff --git a/vendor/github.com/mattermost/platform/model/version.go b/vendor/github.com/mattermost/platform/model/version.go index 4a47f06e..d486f5c5 100644 --- a/vendor/github.com/mattermost/platform/model/version.go +++ b/vendor/github.com/mattermost/platform/model/version.go @@ -13,6 +13,7 @@ import ( // It should be maitained in chronological order with most current // release at the front of the list. var versions = []string{ + "3.1.0", "3.0.0", "2.2.0", "2.1.0", @@ -33,6 +34,7 @@ var CurrentVersion string = versions[0] var BuildNumber string var BuildDate string var BuildHash string +var BuildHashEnterprise string var BuildEnterpriseReady string var versionsWithoutHotFixes []string diff --git a/vendor/golang.org/x/net/lex/httplex/LICENSE b/vendor/golang.org/x/net/lex/httplex/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/golang.org/x/net/lex/httplex/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/net/lex/httplex/httplex.go b/vendor/golang.org/x/net/lex/httplex/httplex.go new file mode 100644 index 00000000..bd0ec24f --- /dev/null +++ b/vendor/golang.org/x/net/lex/httplex/httplex.go @@ -0,0 +1,312 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package httplex contains rules around lexical matters of various +// HTTP-related specifications. +// +// This package is shared by the standard library (which vendors it) +// and x/net/http2. It comes with no API stability promise. +package httplex + +import ( + "strings" + "unicode/utf8" +) + +var isTokenTable = [127]bool{ + '!': true, + '#': true, + '$': true, + '%': true, + '&': true, + '\'': true, + '*': true, + '+': true, + '-': true, + '.': true, + '0': true, + '1': true, + '2': true, + '3': true, + '4': true, + '5': true, + '6': true, + '7': true, + '8': true, + '9': true, + 'A': true, + 'B': true, + 'C': true, + 'D': true, + 'E': true, + 'F': true, + 'G': true, + 'H': true, + 'I': true, + 'J': true, + 'K': true, + 'L': true, + 'M': true, + 'N': true, + 'O': true, + 'P': true, + 'Q': true, + 'R': true, + 'S': true, + 'T': true, + 'U': true, + 'W': true, + 'V': true, + 'X': true, + 'Y': true, + 'Z': true, + '^': true, + '_': true, + '`': true, + 'a': true, + 'b': true, + 'c': true, + 'd': true, + 'e': true, + 'f': true, + 'g': true, + 'h': true, + 'i': true, + 'j': true, + 'k': true, + 'l': true, + 'm': true, + 'n': true, + 'o': true, + 'p': true, + 'q': true, + 'r': true, + 's': true, + 't': true, + 'u': true, + 'v': true, + 'w': true, + 'x': true, + 'y': true, + 'z': true, + '|': true, + '~': true, +} + +func IsTokenRune(r rune) bool { + i := int(r) + return i < len(isTokenTable) && isTokenTable[i] +} + +func isNotToken(r rune) bool { + return !IsTokenRune(r) +} + +// HeaderValuesContainsToken reports whether any string in values +// contains the provided token, ASCII case-insensitively. +func HeaderValuesContainsToken(values []string, token string) bool { + for _, v := range values { + if headerValueContainsToken(v, token) { + return true + } + } + return false +} + +// isOWS reports whether b is an optional whitespace byte, as defined +// by RFC 7230 section 3.2.3. +func isOWS(b byte) bool { return b == ' ' || b == '\t' } + +// trimOWS returns x with all optional whitespace removes from the +// beginning and end. +func trimOWS(x string) string { + // TODO: consider using strings.Trim(x, " \t") instead, + // if and when it's fast enough. See issue 10292. + // But this ASCII-only code will probably always beat UTF-8 + // aware code. + for len(x) > 0 && isOWS(x[0]) { + x = x[1:] + } + for len(x) > 0 && isOWS(x[len(x)-1]) { + x = x[:len(x)-1] + } + return x +} + +// headerValueContainsToken reports whether v (assumed to be a +// 0#element, in the ABNF extension described in RFC 7230 section 7) +// contains token amongst its comma-separated tokens, ASCII +// case-insensitively. +func headerValueContainsToken(v string, token string) bool { + v = trimOWS(v) + if comma := strings.IndexByte(v, ','); comma != -1 { + return tokenEqual(trimOWS(v[:comma]), token) || headerValueContainsToken(v[comma+1:], token) + } + return tokenEqual(v, token) +} + +// lowerASCII returns the ASCII lowercase version of b. +func lowerASCII(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// tokenEqual reports whether t1 and t2 are equal, ASCII case-insensitively. +func tokenEqual(t1, t2 string) bool { + if len(t1) != len(t2) { + return false + } + for i, b := range t1 { + if b >= utf8.RuneSelf { + // No UTF-8 or non-ASCII allowed in tokens. + return false + } + if lowerASCII(byte(b)) != lowerASCII(t2[i]) { + return false + } + } + return true +} + +// isLWS reports whether b is linear white space, according +// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 +// LWS = [CRLF] 1*( SP | HT ) +func isLWS(b byte) bool { return b == ' ' || b == '\t' } + +// isCTL reports whether b is a control byte, according +// to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 +// CTL = +func isCTL(b byte) bool { + const del = 0x7f // a CTL + return b < ' ' || b == del +} + +// ValidHeaderFieldName reports whether v is a valid HTTP/1.x header name. +// HTTP/2 imposes the additional restriction that uppercase ASCII +// letters are not allowed. +// +// RFC 7230 says: +// header-field = field-name ":" OWS field-value OWS +// field-name = token +// token = 1*tchar +// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / +// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +func ValidHeaderFieldName(v string) bool { + if len(v) == 0 { + return false + } + for _, r := range v { + if !IsTokenRune(r) { + return false + } + } + return true +} + +// ValidHostHeader reports whether h is a valid host header. +func ValidHostHeader(h string) bool { + // The latest spec is actually this: + // + // http://tools.ietf.org/html/rfc7230#section-5.4 + // Host = uri-host [ ":" port ] + // + // Where uri-host is: + // http://tools.ietf.org/html/rfc3986#section-3.2.2 + // + // But we're going to be much more lenient for now and just + // search for any byte that's not a valid byte in any of those + // expressions. + for i := 0; i < len(h); i++ { + if !validHostByte[h[i]] { + return false + } + } + return true +} + +// See the validHostHeader comment. +var validHostByte = [256]bool{ + '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, + '8': true, '9': true, + + 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, + 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, + 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, + 'y': true, 'z': true, + + 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, + 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, + 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, + 'Y': true, 'Z': true, + + '!': true, // sub-delims + '$': true, // sub-delims + '%': true, // pct-encoded (and used in IPv6 zones) + '&': true, // sub-delims + '(': true, // sub-delims + ')': true, // sub-delims + '*': true, // sub-delims + '+': true, // sub-delims + ',': true, // sub-delims + '-': true, // unreserved + '.': true, // unreserved + ':': true, // IPv6address + Host expression's optional port + ';': true, // sub-delims + '=': true, // sub-delims + '[': true, + '\'': true, // sub-delims + ']': true, + '_': true, // unreserved + '~': true, // unreserved +} + +// ValidHeaderFieldValue reports whether v is a valid "field-value" according to +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 : +// +// message-header = field-name ":" [ field-value ] +// field-value = *( field-content | LWS ) +// field-content = +// +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 : +// +// TEXT = +// LWS = [CRLF] 1*( SP | HT ) +// CTL = +// +// RFC 7230 says: +// field-value = *( field-content / obs-fold ) +// obj-fold = N/A to http2, and deprecated +// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] +// field-vchar = VCHAR / obs-text +// obs-text = %x80-FF +// VCHAR = "any visible [USASCII] character" +// +// http2 further says: "Similarly, HTTP/2 allows header field values +// that are not valid. While most of the values that can be encoded +// will not alter header field parsing, carriage return (CR, ASCII +// 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII +// 0x0) might be exploited by an attacker if they are translated +// verbatim. Any request or response that contains a character not +// permitted in a header field value MUST be treated as malformed +// (Section 8.1.2.6). Valid characters are defined by the +// field-content ABNF rule in Section 3.2 of [RFC7230]." +// +// This function does not (yet?) properly handle the rejection of +// strings that begin or end with SP or HTAB. +func ValidHeaderFieldValue(v string) bool { + for i := 0; i < len(v); i++ { + b := v[i] + if isCTL(b) && !isLWS(b) { + return false + } + } + return true +} diff --git a/vendor/manifest b/vendor/manifest index 85c206a5..1961eb2a 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -5,7 +5,7 @@ "importpath": "github.com/42wim/matterbridge-plus/bridge", "repository": "https://github.com/42wim/matterbridge-plus", "vcs": "git", - "revision": "e48ce1d820967506c7ea21539f6c88f7a1377d0f", + "revision": "63c7caabf4b92fa47060cf37beb0ed814cefbaea", "branch": "master", "path": "/bridge", "notests": true @@ -14,20 +14,11 @@ "importpath": "github.com/42wim/matterbridge-plus/matterclient", "repository": "https://github.com/42wim/matterbridge-plus", "vcs": "git", - "revision": "e48ce1d820967506c7ea21539f6c88f7a1377d0f", + "revision": "63c7caabf4b92fa47060cf37beb0ed814cefbaea", "branch": "master", "path": "/matterclient", "notests": true }, - { - "importpath": "github.com/42wim/matterbridge/matterhook", - "repository": "https://github.com/42wim/matterbridge", - "vcs": "", - "revision": "6b18257185b1830bd2eff83fae30bdd2055f78b0", - "branch": "master", - "path": "/matterhook", - "notests": true - }, { "importpath": "github.com/Sirupsen/logrus", "repository": "https://github.com/Sirupsen/logrus", @@ -72,8 +63,8 @@ "importpath": "github.com/mattermost/platform/einterfaces", "repository": "https://github.com/mattermost/platform", "vcs": "git", - "revision": "9d94869cc6a0fb9f051879437c104ccd76094380", - "branch": "HEAD", + "revision": "974238231b9cdbd39a825ec8e9299fbb0b51f6b8", + "branch": "release-3.1", "path": "/einterfaces", "notests": true }, @@ -81,8 +72,8 @@ "importpath": "github.com/mattermost/platform/model", "repository": "https://github.com/mattermost/platform", "vcs": "git", - "revision": "9d94869cc6a0fb9f051879437c104ccd76094380", - "branch": "HEAD", + "revision": "974238231b9cdbd39a825ec8e9299fbb0b51f6b8", + "branch": "release-3.1", "path": "/model", "notests": true }, @@ -154,6 +145,15 @@ "path": "/http2/hpack", "notests": true }, + { + "importpath": "golang.org/x/net/lex/httplex", + "repository": "https://go.googlesource.com/net", + "vcs": "git", + "revision": "bc3663df0ac92f928d419e31e0d2af22e683a5a2", + "branch": "master", + "path": "/lex/httplex", + "notests": true + }, { "importpath": "gopkg.in/gcfg.v1", "repository": "https://gopkg.in/gcfg.v1",